0
|
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
2 // Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
|
3
|
|
4 (function(mod) {
|
|
5 if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
6 mod(require("../../lib/codemirror"), require("../javascript/javascript"), require("../css/css"), require("../htmlmixed/htmlmixed"));
|
|
7 else if (typeof define == "function" && define.amd) // AMD
|
|
8 define(["../../lib/codemirror", "../javascript/javascript", "../css/css", "../htmlmixed/htmlmixed"], mod);
|
|
9 else // Plain browser env
|
|
10 mod(CodeMirror);
|
|
11 })(function(CodeMirror) {
|
|
12 "use strict";
|
|
13
|
|
14 CodeMirror.defineMode("pug", function (config) {
|
|
15 // token types
|
|
16 var KEYWORD = 'keyword';
|
|
17 var DOCTYPE = 'meta';
|
|
18 var ID = 'builtin';
|
|
19 var CLASS = 'qualifier';
|
|
20
|
|
21 var ATTRS_NEST = {
|
|
22 '{': '}',
|
|
23 '(': ')',
|
|
24 '[': ']'
|
|
25 };
|
|
26
|
|
27 var jsMode = CodeMirror.getMode(config, 'javascript');
|
|
28
|
|
29 function State() {
|
|
30 this.javaScriptLine = false;
|
|
31 this.javaScriptLineExcludesColon = false;
|
|
32
|
|
33 this.javaScriptArguments = false;
|
|
34 this.javaScriptArgumentsDepth = 0;
|
|
35
|
|
36 this.isInterpolating = false;
|
|
37 this.interpolationNesting = 0;
|
|
38
|
|
39 this.jsState = CodeMirror.startState(jsMode);
|
|
40
|
|
41 this.restOfLine = '';
|
|
42
|
|
43 this.isIncludeFiltered = false;
|
|
44 this.isEach = false;
|
|
45
|
|
46 this.lastTag = '';
|
|
47 this.scriptType = '';
|
|
48
|
|
49 // Attributes Mode
|
|
50 this.isAttrs = false;
|
|
51 this.attrsNest = [];
|
|
52 this.inAttributeName = true;
|
|
53 this.attributeIsType = false;
|
|
54 this.attrValue = '';
|
|
55
|
|
56 // Indented Mode
|
|
57 this.indentOf = Infinity;
|
|
58 this.indentToken = '';
|
|
59
|
|
60 this.innerMode = null;
|
|
61 this.innerState = null;
|
|
62
|
|
63 this.innerModeForLine = false;
|
|
64 }
|
|
65 /**
|
|
66 * Safely copy a state
|
|
67 *
|
|
68 * @return {State}
|
|
69 */
|
|
70 State.prototype.copy = function () {
|
|
71 var res = new State();
|
|
72 res.javaScriptLine = this.javaScriptLine;
|
|
73 res.javaScriptLineExcludesColon = this.javaScriptLineExcludesColon;
|
|
74 res.javaScriptArguments = this.javaScriptArguments;
|
|
75 res.javaScriptArgumentsDepth = this.javaScriptArgumentsDepth;
|
|
76 res.isInterpolating = this.isInterpolating;
|
|
77 res.interpolationNesting = this.interpolationNesting;
|
|
78
|
|
79 res.jsState = CodeMirror.copyState(jsMode, this.jsState);
|
|
80
|
|
81 res.innerMode = this.innerMode;
|
|
82 if (this.innerMode && this.innerState) {
|
|
83 res.innerState = CodeMirror.copyState(this.innerMode, this.innerState);
|
|
84 }
|
|
85
|
|
86 res.restOfLine = this.restOfLine;
|
|
87
|
|
88 res.isIncludeFiltered = this.isIncludeFiltered;
|
|
89 res.isEach = this.isEach;
|
|
90 res.lastTag = this.lastTag;
|
|
91 res.scriptType = this.scriptType;
|
|
92 res.isAttrs = this.isAttrs;
|
|
93 res.attrsNest = this.attrsNest.slice();
|
|
94 res.inAttributeName = this.inAttributeName;
|
|
95 res.attributeIsType = this.attributeIsType;
|
|
96 res.attrValue = this.attrValue;
|
|
97 res.indentOf = this.indentOf;
|
|
98 res.indentToken = this.indentToken;
|
|
99
|
|
100 res.innerModeForLine = this.innerModeForLine;
|
|
101
|
|
102 return res;
|
|
103 };
|
|
104
|
|
105 function javaScript(stream, state) {
|
|
106 if (stream.sol()) {
|
|
107 // if javaScriptLine was set at end of line, ignore it
|
|
108 state.javaScriptLine = false;
|
|
109 state.javaScriptLineExcludesColon = false;
|
|
110 }
|
|
111 if (state.javaScriptLine) {
|
|
112 if (state.javaScriptLineExcludesColon && stream.peek() === ':') {
|
|
113 state.javaScriptLine = false;
|
|
114 state.javaScriptLineExcludesColon = false;
|
|
115 return;
|
|
116 }
|
|
117 var tok = jsMode.token(stream, state.jsState);
|
|
118 if (stream.eol()) state.javaScriptLine = false;
|
|
119 return tok || true;
|
|
120 }
|
|
121 }
|
|
122 function javaScriptArguments(stream, state) {
|
|
123 if (state.javaScriptArguments) {
|
|
124 if (state.javaScriptArgumentsDepth === 0 && stream.peek() !== '(') {
|
|
125 state.javaScriptArguments = false;
|
|
126 return;
|
|
127 }
|
|
128 if (stream.peek() === '(') {
|
|
129 state.javaScriptArgumentsDepth++;
|
|
130 } else if (stream.peek() === ')') {
|
|
131 state.javaScriptArgumentsDepth--;
|
|
132 }
|
|
133 if (state.javaScriptArgumentsDepth === 0) {
|
|
134 state.javaScriptArguments = false;
|
|
135 return;
|
|
136 }
|
|
137
|
|
138 var tok = jsMode.token(stream, state.jsState);
|
|
139 return tok || true;
|
|
140 }
|
|
141 }
|
|
142
|
|
143 function yieldStatement(stream) {
|
|
144 if (stream.match(/^yield\b/)) {
|
|
145 return 'keyword';
|
|
146 }
|
|
147 }
|
|
148
|
|
149 function doctype(stream) {
|
|
150 if (stream.match(/^(?:doctype) *([^\n]+)?/)) {
|
|
151 return DOCTYPE;
|
|
152 }
|
|
153 }
|
|
154
|
|
155 function interpolation(stream, state) {
|
|
156 if (stream.match('#{')) {
|
|
157 state.isInterpolating = true;
|
|
158 state.interpolationNesting = 0;
|
|
159 return 'punctuation';
|
|
160 }
|
|
161 }
|
|
162
|
|
163 function interpolationContinued(stream, state) {
|
|
164 if (state.isInterpolating) {
|
|
165 if (stream.peek() === '}') {
|
|
166 state.interpolationNesting--;
|
|
167 if (state.interpolationNesting < 0) {
|
|
168 stream.next();
|
|
169 state.isInterpolating = false;
|
|
170 return 'punctuation';
|
|
171 }
|
|
172 } else if (stream.peek() === '{') {
|
|
173 state.interpolationNesting++;
|
|
174 }
|
|
175 return jsMode.token(stream, state.jsState) || true;
|
|
176 }
|
|
177 }
|
|
178
|
|
179 function caseStatement(stream, state) {
|
|
180 if (stream.match(/^case\b/)) {
|
|
181 state.javaScriptLine = true;
|
|
182 return KEYWORD;
|
|
183 }
|
|
184 }
|
|
185
|
|
186 function when(stream, state) {
|
|
187 if (stream.match(/^when\b/)) {
|
|
188 state.javaScriptLine = true;
|
|
189 state.javaScriptLineExcludesColon = true;
|
|
190 return KEYWORD;
|
|
191 }
|
|
192 }
|
|
193
|
|
194 function defaultStatement(stream) {
|
|
195 if (stream.match(/^default\b/)) {
|
|
196 return KEYWORD;
|
|
197 }
|
|
198 }
|
|
199
|
|
200 function extendsStatement(stream, state) {
|
|
201 if (stream.match(/^extends?\b/)) {
|
|
202 state.restOfLine = 'string';
|
|
203 return KEYWORD;
|
|
204 }
|
|
205 }
|
|
206
|
|
207 function append(stream, state) {
|
|
208 if (stream.match(/^append\b/)) {
|
|
209 state.restOfLine = 'variable';
|
|
210 return KEYWORD;
|
|
211 }
|
|
212 }
|
|
213 function prepend(stream, state) {
|
|
214 if (stream.match(/^prepend\b/)) {
|
|
215 state.restOfLine = 'variable';
|
|
216 return KEYWORD;
|
|
217 }
|
|
218 }
|
|
219 function block(stream, state) {
|
|
220 if (stream.match(/^block\b *(?:(prepend|append)\b)?/)) {
|
|
221 state.restOfLine = 'variable';
|
|
222 return KEYWORD;
|
|
223 }
|
|
224 }
|
|
225
|
|
226 function include(stream, state) {
|
|
227 if (stream.match(/^include\b/)) {
|
|
228 state.restOfLine = 'string';
|
|
229 return KEYWORD;
|
|
230 }
|
|
231 }
|
|
232
|
|
233 function includeFiltered(stream, state) {
|
|
234 if (stream.match(/^include:([a-zA-Z0-9\-]+)/, false) && stream.match('include')) {
|
|
235 state.isIncludeFiltered = true;
|
|
236 return KEYWORD;
|
|
237 }
|
|
238 }
|
|
239
|
|
240 function includeFilteredContinued(stream, state) {
|
|
241 if (state.isIncludeFiltered) {
|
|
242 var tok = filter(stream, state);
|
|
243 state.isIncludeFiltered = false;
|
|
244 state.restOfLine = 'string';
|
|
245 return tok;
|
|
246 }
|
|
247 }
|
|
248
|
|
249 function mixin(stream, state) {
|
|
250 if (stream.match(/^mixin\b/)) {
|
|
251 state.javaScriptLine = true;
|
|
252 return KEYWORD;
|
|
253 }
|
|
254 }
|
|
255
|
|
256 function call(stream, state) {
|
|
257 if (stream.match(/^\+([-\w]+)/)) {
|
|
258 if (!stream.match(/^\( *[-\w]+ *=/, false)) {
|
|
259 state.javaScriptArguments = true;
|
|
260 state.javaScriptArgumentsDepth = 0;
|
|
261 }
|
|
262 return 'variable';
|
|
263 }
|
|
264 if (stream.match('+#{', false)) {
|
|
265 stream.next();
|
|
266 state.mixinCallAfter = true;
|
|
267 return interpolation(stream, state);
|
|
268 }
|
|
269 }
|
|
270 function callArguments(stream, state) {
|
|
271 if (state.mixinCallAfter) {
|
|
272 state.mixinCallAfter = false;
|
|
273 if (!stream.match(/^\( *[-\w]+ *=/, false)) {
|
|
274 state.javaScriptArguments = true;
|
|
275 state.javaScriptArgumentsDepth = 0;
|
|
276 }
|
|
277 return true;
|
|
278 }
|
|
279 }
|
|
280
|
|
281 function conditional(stream, state) {
|
|
282 if (stream.match(/^(if|unless|else if|else)\b/)) {
|
|
283 state.javaScriptLine = true;
|
|
284 return KEYWORD;
|
|
285 }
|
|
286 }
|
|
287
|
|
288 function each(stream, state) {
|
|
289 if (stream.match(/^(- *)?(each|for)\b/)) {
|
|
290 state.isEach = true;
|
|
291 return KEYWORD;
|
|
292 }
|
|
293 }
|
|
294 function eachContinued(stream, state) {
|
|
295 if (state.isEach) {
|
|
296 if (stream.match(/^ in\b/)) {
|
|
297 state.javaScriptLine = true;
|
|
298 state.isEach = false;
|
|
299 return KEYWORD;
|
|
300 } else if (stream.sol() || stream.eol()) {
|
|
301 state.isEach = false;
|
|
302 } else if (stream.next()) {
|
|
303 while (!stream.match(/^ in\b/, false) && stream.next());
|
|
304 return 'variable';
|
|
305 }
|
|
306 }
|
|
307 }
|
|
308
|
|
309 function whileStatement(stream, state) {
|
|
310 if (stream.match(/^while\b/)) {
|
|
311 state.javaScriptLine = true;
|
|
312 return KEYWORD;
|
|
313 }
|
|
314 }
|
|
315
|
|
316 function tag(stream, state) {
|
|
317 var captures;
|
|
318 if (captures = stream.match(/^(\w(?:[-:\w]*\w)?)\/?/)) {
|
|
319 state.lastTag = captures[1].toLowerCase();
|
|
320 if (state.lastTag === 'script') {
|
|
321 state.scriptType = 'application/javascript';
|
|
322 }
|
|
323 return 'tag';
|
|
324 }
|
|
325 }
|
|
326
|
|
327 function filter(stream, state) {
|
|
328 if (stream.match(/^:([\w\-]+)/)) {
|
|
329 var innerMode;
|
|
330 if (config && config.innerModes) {
|
|
331 innerMode = config.innerModes(stream.current().substring(1));
|
|
332 }
|
|
333 if (!innerMode) {
|
|
334 innerMode = stream.current().substring(1);
|
|
335 }
|
|
336 if (typeof innerMode === 'string') {
|
|
337 innerMode = CodeMirror.getMode(config, innerMode);
|
|
338 }
|
|
339 setInnerMode(stream, state, innerMode);
|
|
340 return 'atom';
|
|
341 }
|
|
342 }
|
|
343
|
|
344 function code(stream, state) {
|
|
345 if (stream.match(/^(!?=|-)/)) {
|
|
346 state.javaScriptLine = true;
|
|
347 return 'punctuation';
|
|
348 }
|
|
349 }
|
|
350
|
|
351 function id(stream) {
|
|
352 if (stream.match(/^#([\w-]+)/)) {
|
|
353 return ID;
|
|
354 }
|
|
355 }
|
|
356
|
|
357 function className(stream) {
|
|
358 if (stream.match(/^\.([\w-]+)/)) {
|
|
359 return CLASS;
|
|
360 }
|
|
361 }
|
|
362
|
|
363 function attrs(stream, state) {
|
|
364 if (stream.peek() == '(') {
|
|
365 stream.next();
|
|
366 state.isAttrs = true;
|
|
367 state.attrsNest = [];
|
|
368 state.inAttributeName = true;
|
|
369 state.attrValue = '';
|
|
370 state.attributeIsType = false;
|
|
371 return 'punctuation';
|
|
372 }
|
|
373 }
|
|
374
|
|
375 function attrsContinued(stream, state) {
|
|
376 if (state.isAttrs) {
|
|
377 if (ATTRS_NEST[stream.peek()]) {
|
|
378 state.attrsNest.push(ATTRS_NEST[stream.peek()]);
|
|
379 }
|
|
380 if (state.attrsNest[state.attrsNest.length - 1] === stream.peek()) {
|
|
381 state.attrsNest.pop();
|
|
382 } else if (stream.eat(')')) {
|
|
383 state.isAttrs = false;
|
|
384 return 'punctuation';
|
|
385 }
|
|
386 if (state.inAttributeName && stream.match(/^[^=,\)!]+/)) {
|
|
387 if (stream.peek() === '=' || stream.peek() === '!') {
|
|
388 state.inAttributeName = false;
|
|
389 state.jsState = CodeMirror.startState(jsMode);
|
|
390 if (state.lastTag === 'script' && stream.current().trim().toLowerCase() === 'type') {
|
|
391 state.attributeIsType = true;
|
|
392 } else {
|
|
393 state.attributeIsType = false;
|
|
394 }
|
|
395 }
|
|
396 return 'attribute';
|
|
397 }
|
|
398
|
|
399 var tok = jsMode.token(stream, state.jsState);
|
|
400 if (state.attributeIsType && tok === 'string') {
|
|
401 state.scriptType = stream.current().toString();
|
|
402 }
|
|
403 if (state.attrsNest.length === 0 && (tok === 'string' || tok === 'variable' || tok === 'keyword')) {
|
|
404 try {
|
|
405 Function('', 'var x ' + state.attrValue.replace(/,\s*$/, '').replace(/^!/, ''));
|
|
406 state.inAttributeName = true;
|
|
407 state.attrValue = '';
|
|
408 stream.backUp(stream.current().length);
|
|
409 return attrsContinued(stream, state);
|
|
410 } catch (ex) {
|
|
411 //not the end of an attribute
|
|
412 }
|
|
413 }
|
|
414 state.attrValue += stream.current();
|
|
415 return tok || true;
|
|
416 }
|
|
417 }
|
|
418
|
|
419 function attributesBlock(stream, state) {
|
|
420 if (stream.match(/^&attributes\b/)) {
|
|
421 state.javaScriptArguments = true;
|
|
422 state.javaScriptArgumentsDepth = 0;
|
|
423 return 'keyword';
|
|
424 }
|
|
425 }
|
|
426
|
|
427 function indent(stream) {
|
|
428 if (stream.sol() && stream.eatSpace()) {
|
|
429 return 'indent';
|
|
430 }
|
|
431 }
|
|
432
|
|
433 function comment(stream, state) {
|
|
434 if (stream.match(/^ *\/\/(-)?([^\n]*)/)) {
|
|
435 state.indentOf = stream.indentation();
|
|
436 state.indentToken = 'comment';
|
|
437 return 'comment';
|
|
438 }
|
|
439 }
|
|
440
|
|
441 function colon(stream) {
|
|
442 if (stream.match(/^: */)) {
|
|
443 return 'colon';
|
|
444 }
|
|
445 }
|
|
446
|
|
447 function text(stream, state) {
|
|
448 if (stream.match(/^(?:\| ?| )([^\n]+)/)) {
|
|
449 return 'string';
|
|
450 }
|
|
451 if (stream.match(/^(<[^\n]*)/, false)) {
|
|
452 // html string
|
|
453 setInnerMode(stream, state, 'htmlmixed');
|
|
454 state.innerModeForLine = true;
|
|
455 return innerMode(stream, state, true);
|
|
456 }
|
|
457 }
|
|
458
|
|
459 function dot(stream, state) {
|
|
460 if (stream.eat('.')) {
|
|
461 var innerMode = null;
|
|
462 if (state.lastTag === 'script' && state.scriptType.toLowerCase().indexOf('javascript') != -1) {
|
|
463 innerMode = state.scriptType.toLowerCase().replace(/"|'/g, '');
|
|
464 } else if (state.lastTag === 'style') {
|
|
465 innerMode = 'css';
|
|
466 }
|
|
467 setInnerMode(stream, state, innerMode);
|
|
468 return 'dot';
|
|
469 }
|
|
470 }
|
|
471
|
|
472 function fail(stream) {
|
|
473 stream.next();
|
|
474 return null;
|
|
475 }
|
|
476
|
|
477
|
|
478 function setInnerMode(stream, state, mode) {
|
|
479 mode = CodeMirror.mimeModes[mode] || mode;
|
|
480 mode = config.innerModes ? config.innerModes(mode) || mode : mode;
|
|
481 mode = CodeMirror.mimeModes[mode] || mode;
|
|
482 mode = CodeMirror.getMode(config, mode);
|
|
483 state.indentOf = stream.indentation();
|
|
484
|
|
485 if (mode && mode.name !== 'null') {
|
|
486 state.innerMode = mode;
|
|
487 } else {
|
|
488 state.indentToken = 'string';
|
|
489 }
|
|
490 }
|
|
491 function innerMode(stream, state, force) {
|
|
492 if (stream.indentation() > state.indentOf || (state.innerModeForLine && !stream.sol()) || force) {
|
|
493 if (state.innerMode) {
|
|
494 if (!state.innerState) {
|
|
495 state.innerState = state.innerMode.startState ? CodeMirror.startState(state.innerMode, stream.indentation()) : {};
|
|
496 }
|
|
497 return stream.hideFirstChars(state.indentOf + 2, function () {
|
|
498 return state.innerMode.token(stream, state.innerState) || true;
|
|
499 });
|
|
500 } else {
|
|
501 stream.skipToEnd();
|
|
502 return state.indentToken;
|
|
503 }
|
|
504 } else if (stream.sol()) {
|
|
505 state.indentOf = Infinity;
|
|
506 state.indentToken = null;
|
|
507 state.innerMode = null;
|
|
508 state.innerState = null;
|
|
509 }
|
|
510 }
|
|
511 function restOfLine(stream, state) {
|
|
512 if (stream.sol()) {
|
|
513 // if restOfLine was set at end of line, ignore it
|
|
514 state.restOfLine = '';
|
|
515 }
|
|
516 if (state.restOfLine) {
|
|
517 stream.skipToEnd();
|
|
518 var tok = state.restOfLine;
|
|
519 state.restOfLine = '';
|
|
520 return tok;
|
|
521 }
|
|
522 }
|
|
523
|
|
524
|
|
525 function startState() {
|
|
526 return new State();
|
|
527 }
|
|
528 function copyState(state) {
|
|
529 return state.copy();
|
|
530 }
|
|
531 /**
|
|
532 * Get the next token in the stream
|
|
533 *
|
|
534 * @param {Stream} stream
|
|
535 * @param {State} state
|
|
536 */
|
|
537 function nextToken(stream, state) {
|
|
538 var tok = innerMode(stream, state)
|
|
539 || restOfLine(stream, state)
|
|
540 || interpolationContinued(stream, state)
|
|
541 || includeFilteredContinued(stream, state)
|
|
542 || eachContinued(stream, state)
|
|
543 || attrsContinued(stream, state)
|
|
544 || javaScript(stream, state)
|
|
545 || javaScriptArguments(stream, state)
|
|
546 || callArguments(stream, state)
|
|
547
|
|
548 || yieldStatement(stream)
|
|
549 || doctype(stream)
|
|
550 || interpolation(stream, state)
|
|
551 || caseStatement(stream, state)
|
|
552 || when(stream, state)
|
|
553 || defaultStatement(stream)
|
|
554 || extendsStatement(stream, state)
|
|
555 || append(stream, state)
|
|
556 || prepend(stream, state)
|
|
557 || block(stream, state)
|
|
558 || include(stream, state)
|
|
559 || includeFiltered(stream, state)
|
|
560 || mixin(stream, state)
|
|
561 || call(stream, state)
|
|
562 || conditional(stream, state)
|
|
563 || each(stream, state)
|
|
564 || whileStatement(stream, state)
|
|
565 || tag(stream, state)
|
|
566 || filter(stream, state)
|
|
567 || code(stream, state)
|
|
568 || id(stream)
|
|
569 || className(stream)
|
|
570 || attrs(stream, state)
|
|
571 || attributesBlock(stream, state)
|
|
572 || indent(stream)
|
|
573 || text(stream, state)
|
|
574 || comment(stream, state)
|
|
575 || colon(stream)
|
|
576 || dot(stream, state)
|
|
577 || fail(stream);
|
|
578
|
|
579 return tok === true ? null : tok;
|
|
580 }
|
|
581 return {
|
|
582 startState: startState,
|
|
583 copyState: copyState,
|
|
584 token: nextToken
|
|
585 };
|
|
586 }, 'javascript', 'css', 'htmlmixed');
|
|
587
|
|
588 CodeMirror.defineMIME('text/x-pug', 'pug');
|
|
589 CodeMirror.defineMIME('text/x-jade', 'pug');
|
|
590
|
|
591 });
|