all repos — NoPaste @ 3e3c433f026c49e04d0efa0f0f39fc688ed32bb1

Resurrected - The PussTheCat.org fork of NoPaste

scripts/CodeMirror/mode/pug/pug.js (view raw)

  1// CodeMirror, copyright (c) by Marijn Haverbeke and others
  2// Distributed under an MIT license: https://codemirror.net/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
 14CodeMirror.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
588CodeMirror.defineMIME('text/x-pug', 'pug');
589CodeMirror.defineMIME('text/x-jade', 'pug');
590
591});