Merge pull request #3232 from ellisonbg/codemirror3
Update to CodeMirror 3 and start to ship our components The main purpose of this branch is the update to CodeMirror 3 and ship it as a bower component. Because CodeMirror doesn't use proper semantic version tags, we have created a CodeMirror ipython fork. The sole purpose of this fork is for us to maintain properly named tags that bower can install from. As a side effect of these changes, we are now shipping in place all bower components that we are using (bootstrap, less. codemirror, jquery). BUT, the version of jquery in components is not yet being used as we are still stuck with the old version attached to the nasty jquery.ui branch we ship. In a later PR, we will need to get rid of that jquery.ui branch (replace those capabilties with bootstrap) and then move to a stable version of jquery.ui that matches that of the jquery component depended upon by bootstrap.pull/37/head
commit
f04e1d8707
@ -0,0 +1,29 @@
|
||||
# IPython Notebook development
|
||||
|
||||
# Development dependencies
|
||||
|
||||
Developers of the IPython Notebook will need to install the following tools:
|
||||
|
||||
* fabric
|
||||
* node.js
|
||||
* less (`npm install -g less`)
|
||||
* bower (`npm install -g bower`)
|
||||
|
||||
# Components
|
||||
|
||||
We are moving to a model where our JavaScript dependencies are managed using
|
||||
[bower](http://bower.io/). These packages are installed in `static/components`
|
||||
and commited into our git repo. Our dependencies are described in the file
|
||||
`static/bower.json`. To update our bower packages, run `fab components` in this
|
||||
directory.
|
||||
|
||||
Because CodeMirror does not use proper semantic versioning for its GitHub tags,
|
||||
we maintain our own fork of CodeMirror that is used with bower. This fork should
|
||||
track the upstream CodeMirror exactly; the only difference is that we are adding
|
||||
semantic versioned tags to our repo.
|
||||
|
||||
# less
|
||||
|
||||
If you edit our `.less` files you will need to run the less compiler to build
|
||||
our minified css files. This can be done by running `fab css` from this directory.
|
||||
|
||||
@ -0,0 +1,345 @@
|
||||
// This is an ipython mode for CodeMirror. We started from the CM Python mode and renamed
|
||||
// it to ipython. We have then marked all other changes we have made to the file.
|
||||
|
||||
CodeMirror.defineMode("ipython", function(conf, parserConf) {
|
||||
var ERRORCLASS = 'error';
|
||||
|
||||
function wordRegexp(words) {
|
||||
return new RegExp("^((" + words.join(")|(") + "))\\b");
|
||||
}
|
||||
|
||||
// IPython-specific changes: add '?' as recognized character using \\?
|
||||
var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
|
||||
// End IPython changes.
|
||||
var singleDelimiters = parserConf.singleDelimiters || new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
|
||||
var doubleOperators = parserConf.doubleOperators || new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
|
||||
var doubleDelimiters = parserConf.doubleDelimiters || new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
|
||||
var tripleDelimiters = parserConf.tripleDelimiters || new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
|
||||
var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
|
||||
|
||||
var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']);
|
||||
var commonkeywords = ['as', 'assert', 'break', 'class', 'continue',
|
||||
'def', 'del', 'elif', 'else', 'except', 'finally',
|
||||
'for', 'from', 'global', 'if', 'import',
|
||||
'lambda', 'pass', 'raise', 'return',
|
||||
'try', 'while', 'with', 'yield'];
|
||||
var commonBuiltins = ['abs', 'all', 'any', 'bin', 'bool', 'bytearray', 'callable', 'chr',
|
||||
'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
|
||||
'enumerate', 'eval', 'filter', 'float', 'format', 'frozenset',
|
||||
'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id',
|
||||
'input', 'int', 'isinstance', 'issubclass', 'iter', 'len',
|
||||
'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next',
|
||||
'object', 'oct', 'open', 'ord', 'pow', 'property', 'range',
|
||||
'repr', 'reversed', 'round', 'set', 'setattr', 'slice',
|
||||
'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple',
|
||||
'type', 'vars', 'zip', '__import__', 'NotImplemented',
|
||||
'Ellipsis', '__debug__'];
|
||||
var py2 = {'builtins': ['apply', 'basestring', 'buffer', 'cmp', 'coerce', 'execfile',
|
||||
'file', 'intern', 'long', 'raw_input', 'reduce', 'reload',
|
||||
'unichr', 'unicode', 'xrange', 'False', 'True', 'None'],
|
||||
'keywords': ['exec', 'print']};
|
||||
var py3 = {'builtins': ['ascii', 'bytes', 'exec', 'print'],
|
||||
'keywords': ['nonlocal', 'False', 'True', 'None']};
|
||||
|
||||
if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) {
|
||||
commonkeywords = commonkeywords.concat(py3.keywords);
|
||||
commonBuiltins = commonBuiltins.concat(py3.builtins);
|
||||
var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i");
|
||||
} else {
|
||||
commonkeywords = commonkeywords.concat(py2.keywords);
|
||||
commonBuiltins = commonBuiltins.concat(py2.builtins);
|
||||
var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
|
||||
}
|
||||
var keywords = wordRegexp(commonkeywords);
|
||||
var builtins = wordRegexp(commonBuiltins);
|
||||
|
||||
var indentInfo = null;
|
||||
|
||||
// tokenizers
|
||||
function tokenBase(stream, state) {
|
||||
// Handle scope changes
|
||||
if (stream.sol()) {
|
||||
var scopeOffset = state.scopes[0].offset;
|
||||
if (stream.eatSpace()) {
|
||||
var lineOffset = stream.indentation();
|
||||
if (lineOffset > scopeOffset) {
|
||||
indentInfo = 'indent';
|
||||
} else if (lineOffset < scopeOffset) {
|
||||
indentInfo = 'dedent';
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
if (scopeOffset > 0) {
|
||||
dedent(stream, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stream.eatSpace()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var ch = stream.peek();
|
||||
|
||||
// Handle Comments
|
||||
if (ch === '#') {
|
||||
stream.skipToEnd();
|
||||
return 'comment';
|
||||
}
|
||||
|
||||
// Handle Number Literals
|
||||
if (stream.match(/^[0-9\.]/, false)) {
|
||||
var floatLiteral = false;
|
||||
// Floats
|
||||
if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; }
|
||||
if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
|
||||
if (stream.match(/^\.\d+/)) { floatLiteral = true; }
|
||||
if (floatLiteral) {
|
||||
// Float literals may be "imaginary"
|
||||
stream.eat(/J/i);
|
||||
return 'number';
|
||||
}
|
||||
// Integers
|
||||
var intLiteral = false;
|
||||
// Hex
|
||||
if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; }
|
||||
// Binary
|
||||
if (stream.match(/^0b[01]+/i)) { intLiteral = true; }
|
||||
// Octal
|
||||
if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; }
|
||||
// Decimal
|
||||
if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
|
||||
// Decimal literals may be "imaginary"
|
||||
stream.eat(/J/i);
|
||||
// TODO - Can you have imaginary longs?
|
||||
intLiteral = true;
|
||||
}
|
||||
// Zero by itself with no other piece of number.
|
||||
if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
|
||||
if (intLiteral) {
|
||||
// Integer literals may be "long"
|
||||
stream.eat(/L/i);
|
||||
return 'number';
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Strings
|
||||
if (stream.match(stringPrefixes)) {
|
||||
state.tokenize = tokenStringFactory(stream.current());
|
||||
return state.tokenize(stream, state);
|
||||
}
|
||||
|
||||
// Handle operators and Delimiters
|
||||
if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
|
||||
return null;
|
||||
}
|
||||
if (stream.match(doubleOperators)
|
||||
|| stream.match(singleOperators)
|
||||
|| stream.match(wordOperators)) {
|
||||
return 'operator';
|
||||
}
|
||||
if (stream.match(singleDelimiters)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stream.match(keywords)) {
|
||||
return 'keyword';
|
||||
}
|
||||
|
||||
if (stream.match(builtins)) {
|
||||
return 'builtin';
|
||||
}
|
||||
|
||||
if (stream.match(identifiers)) {
|
||||
return 'variable';
|
||||
}
|
||||
|
||||
// Handle non-detected items
|
||||
stream.next();
|
||||
return ERRORCLASS;
|
||||
}
|
||||
|
||||
function tokenStringFactory(delimiter) {
|
||||
while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) {
|
||||
delimiter = delimiter.substr(1);
|
||||
}
|
||||
var singleline = delimiter.length == 1;
|
||||
var OUTCLASS = 'string';
|
||||
|
||||
function tokenString(stream, state) {
|
||||
while (!stream.eol()) {
|
||||
stream.eatWhile(/[^'"\\]/);
|
||||
if (stream.eat('\\')) {
|
||||
stream.next();
|
||||
if (singleline && stream.eol()) {
|
||||
return OUTCLASS;
|
||||
}
|
||||
} else if (stream.match(delimiter)) {
|
||||
state.tokenize = tokenBase;
|
||||
return OUTCLASS;
|
||||
} else {
|
||||
stream.eat(/['"]/);
|
||||
}
|
||||
}
|
||||
if (singleline) {
|
||||
if (parserConf.singleLineStringErrors) {
|
||||
return ERRORCLASS;
|
||||
} else {
|
||||
state.tokenize = tokenBase;
|
||||
}
|
||||
}
|
||||
return OUTCLASS;
|
||||
}
|
||||
tokenString.isString = true;
|
||||
return tokenString;
|
||||
}
|
||||
|
||||
function indent(stream, state, type) {
|
||||
type = type || 'py';
|
||||
var indentUnit = 0;
|
||||
if (type === 'py') {
|
||||
if (state.scopes[0].type !== 'py') {
|
||||
state.scopes[0].offset = stream.indentation();
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < state.scopes.length; ++i) {
|
||||
if (state.scopes[i].type === 'py') {
|
||||
indentUnit = state.scopes[i].offset + conf.indentUnit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
indentUnit = stream.column() + stream.current().length;
|
||||
}
|
||||
state.scopes.unshift({
|
||||
offset: indentUnit,
|
||||
type: type
|
||||
});
|
||||
}
|
||||
|
||||
function dedent(stream, state, type) {
|
||||
type = type || 'py';
|
||||
if (state.scopes.length == 1) return;
|
||||
if (state.scopes[0].type === 'py') {
|
||||
var _indent = stream.indentation();
|
||||
var _indent_index = -1;
|
||||
for (var i = 0; i < state.scopes.length; ++i) {
|
||||
if (_indent === state.scopes[i].offset) {
|
||||
_indent_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_indent_index === -1) {
|
||||
return true;
|
||||
}
|
||||
while (state.scopes[0].offset !== _indent) {
|
||||
state.scopes.shift();
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
if (type === 'py') {
|
||||
state.scopes[0].offset = stream.indentation();
|
||||
return false;
|
||||
} else {
|
||||
if (state.scopes[0].type != type) {
|
||||
return true;
|
||||
}
|
||||
state.scopes.shift();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function tokenLexer(stream, state) {
|
||||
indentInfo = null;
|
||||
var style = state.tokenize(stream, state);
|
||||
var current = stream.current();
|
||||
|
||||
// Handle '.' connected identifiers
|
||||
if (current === '.') {
|
||||
style = stream.match(identifiers, false) ? null : ERRORCLASS;
|
||||
if (style === null && state.lastToken === 'meta') {
|
||||
// Apply 'meta' style to '.' connected identifiers when
|
||||
// appropriate.
|
||||
style = 'meta';
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
// Handle decorators
|
||||
if (current === '@') {
|
||||
return stream.match(identifiers, false) ? 'meta' : ERRORCLASS;
|
||||
}
|
||||
|
||||
if ((style === 'variable' || style === 'builtin')
|
||||
&& state.lastToken === 'meta') {
|
||||
style = 'meta';
|
||||
}
|
||||
|
||||
// Handle scope changes.
|
||||
if (current === 'pass' || current === 'return') {
|
||||
state.dedent += 1;
|
||||
}
|
||||
if (current === 'lambda') state.lambda = true;
|
||||
if ((current === ':' && !state.lambda && state.scopes[0].type == 'py')
|
||||
|| indentInfo === 'indent') {
|
||||
indent(stream, state);
|
||||
}
|
||||
var delimiter_index = '[({'.indexOf(current);
|
||||
if (delimiter_index !== -1) {
|
||||
indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
|
||||
}
|
||||
if (indentInfo === 'dedent') {
|
||||
if (dedent(stream, state)) {
|
||||
return ERRORCLASS;
|
||||
}
|
||||
}
|
||||
delimiter_index = '])}'.indexOf(current);
|
||||
if (delimiter_index !== -1) {
|
||||
if (dedent(stream, state, current)) {
|
||||
return ERRORCLASS;
|
||||
}
|
||||
}
|
||||
if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'py') {
|
||||
if (state.scopes.length > 1) state.scopes.shift();
|
||||
state.dedent -= 1;
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
var external = {
|
||||
startState: function(basecolumn) {
|
||||
return {
|
||||
tokenize: tokenBase,
|
||||
scopes: [{offset:basecolumn || 0, type:'py'}],
|
||||
lastToken: null,
|
||||
lambda: false,
|
||||
dedent: 0
|
||||
};
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
var style = tokenLexer(stream, state);
|
||||
|
||||
state.lastToken = style;
|
||||
|
||||
if (stream.eol() && stream.lambda) {
|
||||
state.lambda = false;
|
||||
}
|
||||
|
||||
return style;
|
||||
},
|
||||
|
||||
indent: function(state) {
|
||||
if (state.tokenize != tokenBase) {
|
||||
return state.tokenize.isString ? CodeMirror.Pass : 0;
|
||||
}
|
||||
|
||||
return state.scopes[0].offset;
|
||||
}
|
||||
|
||||
};
|
||||
return external;
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("text/x-ipython", "ipython");
|
||||
Loading…
Reference in new issue