##// END OF EJS Templates
IPython-specific changes to CodeMirror: recognize '?' in Python mode.
Fernando Perez -
Show More
@@ -1,320 +1,324 b''
1 CodeMirror.defineMode("python", function(conf, parserConf) {
1 CodeMirror.defineMode("python", function(conf, parserConf) {
2 var ERRORCLASS = 'error';
2 var ERRORCLASS = 'error';
3
3
4 function wordRegexp(words) {
4 function wordRegexp(words) {
5 return new RegExp("^((" + words.join(")|(") + "))\\b");
5 return new RegExp("^((" + words.join(")|(") + "))\\b");
6 }
6 }
7
7
8 var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
8 // IPython-specific changes: add '?' as recognized character.
9 //var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
10 var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\\?]");
11 // End IPython changes.
12
9 var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
13 var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
10 var doubleOperators = new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
14 var doubleOperators = new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
11 var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
15 var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
12 var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
16 var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
13 var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
17 var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
14
18
15 var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']);
19 var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']);
16 var commonkeywords = ['as', 'assert', 'break', 'class', 'continue',
20 var commonkeywords = ['as', 'assert', 'break', 'class', 'continue',
17 'def', 'del', 'elif', 'else', 'except', 'finally',
21 'def', 'del', 'elif', 'else', 'except', 'finally',
18 'for', 'from', 'global', 'if', 'import',
22 'for', 'from', 'global', 'if', 'import',
19 'lambda', 'pass', 'raise', 'return',
23 'lambda', 'pass', 'raise', 'return',
20 'try', 'while', 'with', 'yield'];
24 'try', 'while', 'with', 'yield'];
21 var commontypes = ['bool', 'classmethod', 'complex', 'dict', 'enumerate',
25 var commontypes = ['bool', 'classmethod', 'complex', 'dict', 'enumerate',
22 'float', 'frozenset', 'int', 'list', 'object',
26 'float', 'frozenset', 'int', 'list', 'object',
23 'property', 'reversed', 'set', 'slice', 'staticmethod',
27 'property', 'reversed', 'set', 'slice', 'staticmethod',
24 'str', 'super', 'tuple', 'type'];
28 'str', 'super', 'tuple', 'type'];
25 var py2 = {'types': ['basestring', 'buffer', 'file', 'long', 'unicode',
29 var py2 = {'types': ['basestring', 'buffer', 'file', 'long', 'unicode',
26 'xrange'],
30 'xrange'],
27 'keywords': ['exec', 'print']};
31 'keywords': ['exec', 'print']};
28 var py3 = {'types': ['bytearray', 'bytes', 'filter', 'map', 'memoryview',
32 var py3 = {'types': ['bytearray', 'bytes', 'filter', 'map', 'memoryview',
29 'open', 'range', 'zip'],
33 'open', 'range', 'zip'],
30 'keywords': ['nonlocal']};
34 'keywords': ['nonlocal']};
31
35
32 if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) {
36 if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) {
33 commonkeywords = commonkeywords.concat(py3.keywords);
37 commonkeywords = commonkeywords.concat(py3.keywords);
34 commontypes = commontypes.concat(py3.types);
38 commontypes = commontypes.concat(py3.types);
35 var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i");
39 var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i");
36 } else {
40 } else {
37 commonkeywords = commonkeywords.concat(py2.keywords);
41 commonkeywords = commonkeywords.concat(py2.keywords);
38 commontypes = commontypes.concat(py2.types);
42 commontypes = commontypes.concat(py2.types);
39 var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
43 var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
40 }
44 }
41 var keywords = wordRegexp(commonkeywords);
45 var keywords = wordRegexp(commonkeywords);
42 var types = wordRegexp(commontypes);
46 var types = wordRegexp(commontypes);
43
47
44 var indentInfo = null;
48 var indentInfo = null;
45
49
46 // tokenizers
50 // tokenizers
47 function tokenBase(stream, state) {
51 function tokenBase(stream, state) {
48 // Handle scope changes
52 // Handle scope changes
49 if (stream.sol()) {
53 if (stream.sol()) {
50 var scopeOffset = state.scopes[0].offset;
54 var scopeOffset = state.scopes[0].offset;
51 if (stream.eatSpace()) {
55 if (stream.eatSpace()) {
52 var lineOffset = stream.indentation();
56 var lineOffset = stream.indentation();
53 if (lineOffset > scopeOffset) {
57 if (lineOffset > scopeOffset) {
54 indentInfo = 'indent';
58 indentInfo = 'indent';
55 } else if (lineOffset < scopeOffset) {
59 } else if (lineOffset < scopeOffset) {
56 indentInfo = 'dedent';
60 indentInfo = 'dedent';
57 }
61 }
58 return null;
62 return null;
59 } else {
63 } else {
60 if (scopeOffset > 0) {
64 if (scopeOffset > 0) {
61 dedent(stream, state);
65 dedent(stream, state);
62 }
66 }
63 }
67 }
64 }
68 }
65 if (stream.eatSpace()) {
69 if (stream.eatSpace()) {
66 return null;
70 return null;
67 }
71 }
68
72
69 var ch = stream.peek();
73 var ch = stream.peek();
70
74
71 // Handle Comments
75 // Handle Comments
72 if (ch === '#') {
76 if (ch === '#') {
73 stream.skipToEnd();
77 stream.skipToEnd();
74 return 'comment';
78 return 'comment';
75 }
79 }
76
80
77 // Handle Number Literals
81 // Handle Number Literals
78 if (stream.match(/^[0-9\.]/, false)) {
82 if (stream.match(/^[0-9\.]/, false)) {
79 var floatLiteral = false;
83 var floatLiteral = false;
80 // Floats
84 // Floats
81 if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; }
85 if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; }
82 if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
86 if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
83 if (stream.match(/^\.\d+/)) { floatLiteral = true; }
87 if (stream.match(/^\.\d+/)) { floatLiteral = true; }
84 if (floatLiteral) {
88 if (floatLiteral) {
85 // Float literals may be "imaginary"
89 // Float literals may be "imaginary"
86 stream.eat(/J/i);
90 stream.eat(/J/i);
87 return 'number';
91 return 'number';
88 }
92 }
89 // Integers
93 // Integers
90 var intLiteral = false;
94 var intLiteral = false;
91 // Hex
95 // Hex
92 if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; }
96 if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; }
93 // Binary
97 // Binary
94 if (stream.match(/^0b[01]+/i)) { intLiteral = true; }
98 if (stream.match(/^0b[01]+/i)) { intLiteral = true; }
95 // Octal
99 // Octal
96 if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; }
100 if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; }
97 // Decimal
101 // Decimal
98 if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
102 if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
99 // Decimal literals may be "imaginary"
103 // Decimal literals may be "imaginary"
100 stream.eat(/J/i);
104 stream.eat(/J/i);
101 // TODO - Can you have imaginary longs?
105 // TODO - Can you have imaginary longs?
102 intLiteral = true;
106 intLiteral = true;
103 }
107 }
104 // Zero by itself with no other piece of number.
108 // Zero by itself with no other piece of number.
105 if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
109 if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; }
106 if (intLiteral) {
110 if (intLiteral) {
107 // Integer literals may be "long"
111 // Integer literals may be "long"
108 stream.eat(/L/i);
112 stream.eat(/L/i);
109 return 'number';
113 return 'number';
110 }
114 }
111 }
115 }
112
116
113 // Handle Strings
117 // Handle Strings
114 if (stream.match(stringPrefixes)) {
118 if (stream.match(stringPrefixes)) {
115 state.tokenize = tokenStringFactory(stream.current());
119 state.tokenize = tokenStringFactory(stream.current());
116 return state.tokenize(stream, state);
120 return state.tokenize(stream, state);
117 }
121 }
118
122
119 // Handle operators and Delimiters
123 // Handle operators and Delimiters
120 if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
124 if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
121 return null;
125 return null;
122 }
126 }
123 if (stream.match(doubleOperators)
127 if (stream.match(doubleOperators)
124 || stream.match(singleOperators)
128 || stream.match(singleOperators)
125 || stream.match(wordOperators)) {
129 || stream.match(wordOperators)) {
126 return 'operator';
130 return 'operator';
127 }
131 }
128 if (stream.match(singleDelimiters)) {
132 if (stream.match(singleDelimiters)) {
129 return null;
133 return null;
130 }
134 }
131
135
132 if (stream.match(types)) {
136 if (stream.match(types)) {
133 return 'builtin';
137 return 'builtin';
134 }
138 }
135
139
136 if (stream.match(keywords)) {
140 if (stream.match(keywords)) {
137 return 'keyword';
141 return 'keyword';
138 }
142 }
139
143
140 if (stream.match(identifiers)) {
144 if (stream.match(identifiers)) {
141 return 'variable';
145 return 'variable';
142 }
146 }
143
147
144 // Handle non-detected items
148 // Handle non-detected items
145 stream.next();
149 stream.next();
146 return ERRORCLASS;
150 return ERRORCLASS;
147 }
151 }
148
152
149 function tokenStringFactory(delimiter) {
153 function tokenStringFactory(delimiter) {
150 while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) {
154 while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) {
151 delimiter = delimiter.substr(1);
155 delimiter = delimiter.substr(1);
152 }
156 }
153 var singleline = delimiter.length == 1;
157 var singleline = delimiter.length == 1;
154 var OUTCLASS = 'string';
158 var OUTCLASS = 'string';
155
159
156 return function tokenString(stream, state) {
160 return function tokenString(stream, state) {
157 while (!stream.eol()) {
161 while (!stream.eol()) {
158 stream.eatWhile(/[^'"\\]/);
162 stream.eatWhile(/[^'"\\]/);
159 if (stream.eat('\\')) {
163 if (stream.eat('\\')) {
160 stream.next();
164 stream.next();
161 if (singleline && stream.eol()) {
165 if (singleline && stream.eol()) {
162 return OUTCLASS;
166 return OUTCLASS;
163 }
167 }
164 } else if (stream.match(delimiter)) {
168 } else if (stream.match(delimiter)) {
165 state.tokenize = tokenBase;
169 state.tokenize = tokenBase;
166 return OUTCLASS;
170 return OUTCLASS;
167 } else {
171 } else {
168 stream.eat(/['"]/);
172 stream.eat(/['"]/);
169 }
173 }
170 }
174 }
171 if (singleline) {
175 if (singleline) {
172 if (parserConf.singleLineStringErrors) {
176 if (parserConf.singleLineStringErrors) {
173 return ERRORCLASS;
177 return ERRORCLASS;
174 } else {
178 } else {
175 state.tokenize = tokenBase;
179 state.tokenize = tokenBase;
176 }
180 }
177 }
181 }
178 return OUTCLASS;
182 return OUTCLASS;
179 };
183 };
180 }
184 }
181
185
182 function indent(stream, state, type) {
186 function indent(stream, state, type) {
183 type = type || 'py';
187 type = type || 'py';
184 var indentUnit = 0;
188 var indentUnit = 0;
185 if (type === 'py') {
189 if (type === 'py') {
186 for (var i = 0; i < state.scopes.length; ++i) {
190 for (var i = 0; i < state.scopes.length; ++i) {
187 if (state.scopes[i].type === 'py') {
191 if (state.scopes[i].type === 'py') {
188 indentUnit = state.scopes[i].offset + conf.indentUnit;
192 indentUnit = state.scopes[i].offset + conf.indentUnit;
189 break;
193 break;
190 }
194 }
191 }
195 }
192 } else {
196 } else {
193 indentUnit = stream.column() + stream.current().length;
197 indentUnit = stream.column() + stream.current().length;
194 }
198 }
195 state.scopes.unshift({
199 state.scopes.unshift({
196 offset: indentUnit,
200 offset: indentUnit,
197 type: type
201 type: type
198 });
202 });
199 }
203 }
200
204
201 function dedent(stream, state) {
205 function dedent(stream, state) {
202 if (state.scopes.length == 1) return;
206 if (state.scopes.length == 1) return;
203 if (state.scopes[0].type === 'py') {
207 if (state.scopes[0].type === 'py') {
204 var _indent = stream.indentation();
208 var _indent = stream.indentation();
205 var _indent_index = -1;
209 var _indent_index = -1;
206 for (var i = 0; i < state.scopes.length; ++i) {
210 for (var i = 0; i < state.scopes.length; ++i) {
207 if (_indent === state.scopes[i].offset) {
211 if (_indent === state.scopes[i].offset) {
208 _indent_index = i;
212 _indent_index = i;
209 break;
213 break;
210 }
214 }
211 }
215 }
212 if (_indent_index === -1) {
216 if (_indent_index === -1) {
213 return true;
217 return true;
214 }
218 }
215 while (state.scopes[0].offset !== _indent) {
219 while (state.scopes[0].offset !== _indent) {
216 state.scopes.shift();
220 state.scopes.shift();
217 }
221 }
218 return false
222 return false
219 } else {
223 } else {
220 state.scopes.shift();
224 state.scopes.shift();
221 return false;
225 return false;
222 }
226 }
223 }
227 }
224
228
225 function tokenLexer(stream, state) {
229 function tokenLexer(stream, state) {
226 indentInfo = null;
230 indentInfo = null;
227 var style = state.tokenize(stream, state);
231 var style = state.tokenize(stream, state);
228 var current = stream.current();
232 var current = stream.current();
229
233
230 // Handle '.' connected identifiers
234 // Handle '.' connected identifiers
231 if (current === '.') {
235 if (current === '.') {
232 style = state.tokenize(stream, state);
236 style = state.tokenize(stream, state);
233 current = stream.current();
237 current = stream.current();
234 if (style === 'variable') {
238 if (style === 'variable') {
235 return 'variable';
239 return 'variable';
236 } else {
240 } else {
237 return ERRORCLASS;
241 return ERRORCLASS;
238 }
242 }
239 }
243 }
240
244
241 // Handle decorators
245 // Handle decorators
242 if (current === '@') {
246 if (current === '@') {
243 style = state.tokenize(stream, state);
247 style = state.tokenize(stream, state);
244 current = stream.current();
248 current = stream.current();
245 if (style === 'variable'
249 if (style === 'variable'
246 || current === '@staticmethod'
250 || current === '@staticmethod'
247 || current === '@classmethod') {
251 || current === '@classmethod') {
248 return 'meta';
252 return 'meta';
249 } else {
253 } else {
250 return ERRORCLASS;
254 return ERRORCLASS;
251 }
255 }
252 }
256 }
253
257
254 // Handle scope changes.
258 // Handle scope changes.
255 if (current === 'pass' || current === 'return') {
259 if (current === 'pass' || current === 'return') {
256 state.dedent += 1;
260 state.dedent += 1;
257 }
261 }
258 if ((current === ':' && !state.lambda && state.scopes[0].type == 'py')
262 if ((current === ':' && !state.lambda && state.scopes[0].type == 'py')
259 || indentInfo === 'indent') {
263 || indentInfo === 'indent') {
260 indent(stream, state);
264 indent(stream, state);
261 }
265 }
262 var delimiter_index = '[({'.indexOf(current);
266 var delimiter_index = '[({'.indexOf(current);
263 if (delimiter_index !== -1) {
267 if (delimiter_index !== -1) {
264 indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
268 indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
265 }
269 }
266 if (indentInfo === 'dedent') {
270 if (indentInfo === 'dedent') {
267 if (dedent(stream, state)) {
271 if (dedent(stream, state)) {
268 return ERRORCLASS;
272 return ERRORCLASS;
269 }
273 }
270 }
274 }
271 delimiter_index = '])}'.indexOf(current);
275 delimiter_index = '])}'.indexOf(current);
272 if (delimiter_index !== -1) {
276 if (delimiter_index !== -1) {
273 if (dedent(stream, state)) {
277 if (dedent(stream, state)) {
274 return ERRORCLASS;
278 return ERRORCLASS;
275 }
279 }
276 }
280 }
277 if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'py') {
281 if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'py') {
278 if (state.scopes.length > 1) state.scopes.shift();
282 if (state.scopes.length > 1) state.scopes.shift();
279 state.dedent -= 1;
283 state.dedent -= 1;
280 }
284 }
281
285
282 return style;
286 return style;
283 }
287 }
284
288
285 var external = {
289 var external = {
286 startState: function(basecolumn) {
290 startState: function(basecolumn) {
287 return {
291 return {
288 tokenize: tokenBase,
292 tokenize: tokenBase,
289 scopes: [{offset:basecolumn || 0, type:'py'}],
293 scopes: [{offset:basecolumn || 0, type:'py'}],
290 lastToken: null,
294 lastToken: null,
291 lambda: false,
295 lambda: false,
292 dedent: 0
296 dedent: 0
293 };
297 };
294 },
298 },
295
299
296 token: function(stream, state) {
300 token: function(stream, state) {
297 var style = tokenLexer(stream, state);
301 var style = tokenLexer(stream, state);
298
302
299 state.lastToken = {style:style, content: stream.current()};
303 state.lastToken = {style:style, content: stream.current()};
300
304
301 if (stream.eol() && stream.lambda) {
305 if (stream.eol() && stream.lambda) {
302 state.lambda = false;
306 state.lambda = false;
303 }
307 }
304
308
305 return style;
309 return style;
306 },
310 },
307
311
308 indent: function(state, textAfter) {
312 indent: function(state, textAfter) {
309 if (state.tokenize != tokenBase) {
313 if (state.tokenize != tokenBase) {
310 return 0;
314 return 0;
311 }
315 }
312
316
313 return state.scopes[0].offset;
317 return state.scopes[0].offset;
314 }
318 }
315
319
316 };
320 };
317 return external;
321 return external;
318 });
322 });
319
323
320 CodeMirror.defineMIME("text/x-python", "python");
324 CodeMirror.defineMIME("text/x-python", "python");
General Comments 0
You need to be logged in to leave comments. Login now