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