1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: http://codemirror.net/LICENSE
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
11 })(function(CodeMirror) {
14 CodeMirror.defineMode('jade', function (config) {
16 var KEYWORD = 'keyword';
19 var CLASS = 'qualifier';
27 var jsMode = CodeMirror.getMode(config, 'javascript');
30 this.javaScriptLine = false;
31 this.javaScriptLineExcludesColon = false;
33 this.javaScriptArguments = false;
34 this.javaScriptArgumentsDepth = 0;
36 this.isInterpolating = false;
37 this.interpolationNesting = 0;
39 this.jsState = jsMode.startState();
43 this.isIncludeFiltered = false;
52 this.inAttributeName = true;
53 this.attributeIsType = false;
57 this.indentOf = Infinity;
58 this.indentToken = '';
60 this.innerMode = null;
61 this.innerState = null;
63 this.innerModeForLine = false;
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.intpolationNesting;
79 res.jsState = CodeMirror.copyState(jsMode, this.jsState);
81 res.innerMode = this.innerMode;
82 if (this.innerMode && this.innerState) {
83 res.innerState = CodeMirror.copyState(this.innerMode, this.innerState);
86 res.restOfLine = this.restOfLine;
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;
100 res.innerModeForLine = this.innerModeForLine;
105 function javaScript(stream, state) {
107 // if javaScriptLine was set at end of line, ignore it
108 state.javaScriptLine = false;
109 state.javaScriptLineExcludesColon = false;
111 if (state.javaScriptLine) {
112 if (state.javaScriptLineExcludesColon && stream.peek() === ':') {
113 state.javaScriptLine = false;
114 state.javaScriptLineExcludesColon = false;
117 var tok = jsMode.token(stream, state.jsState);
118 if (stream.eol()) state.javaScriptLine = false;
122 function javaScriptArguments(stream, state) {
123 if (state.javaScriptArguments) {
124 if (state.javaScriptArgumentsDepth === 0 && stream.peek() !== '(') {
125 state.javaScriptArguments = false;
128 if (stream.peek() === '(') {
129 state.javaScriptArgumentsDepth++;
130 } else if (stream.peek() === ')') {
131 state.javaScriptArgumentsDepth--;
133 if (state.javaScriptArgumentsDepth === 0) {
134 state.javaScriptArguments = false;
138 var tok = jsMode.token(stream, state.jsState);
143 function yieldStatement(stream) {
144 if (stream.match(/^yield\b/)) {
149 function doctype(stream) {
150 if (stream.match(/^(?:doctype) *([^\n]+)?/)) {
155 function interpolation(stream, state) {
156 if (stream.match('#{')) {
157 state.isInterpolating = true;
158 state.interpolationNesting = 0;
159 return 'punctuation';
163 function interpolationContinued(stream, state) {
164 if (state.isInterpolating) {
165 if (stream.peek() === '}') {
166 state.interpolationNesting--;
167 if (state.interpolationNesting < 0) {
169 state.isInterpolating = false;
170 return 'puncutation';
172 } else if (stream.peek() === '{') {
173 state.interpolationNesting++;
175 return jsMode.token(stream, state.jsState) || true;
179 function caseStatement(stream, state) {
180 if (stream.match(/^case\b/)) {
181 state.javaScriptLine = true;
186 function when(stream, state) {
187 if (stream.match(/^when\b/)) {
188 state.javaScriptLine = true;
189 state.javaScriptLineExcludesColon = true;
194 function defaultStatement(stream) {
195 if (stream.match(/^default\b/)) {
200 function extendsStatement(stream, state) {
201 if (stream.match(/^extends?\b/)) {
202 state.restOfLine = 'string';
207 function append(stream, state) {
208 if (stream.match(/^append\b/)) {
209 state.restOfLine = 'variable';
213 function prepend(stream, state) {
214 if (stream.match(/^prepend\b/)) {
215 state.restOfLine = 'variable';
219 function block(stream, state) {
220 if (stream.match(/^block\b *(?:(prepend|append)\b)?/)) {
221 state.restOfLine = 'variable';
226 function include(stream, state) {
227 if (stream.match(/^include\b/)) {
228 state.restOfLine = 'string';
233 function includeFiltered(stream, state) {
234 if (stream.match(/^include:([a-zA-Z0-9\-]+)/, false) && stream.match('include')) {
235 state.isIncludeFiltered = true;
240 function includeFilteredContinued(stream, state) {
241 if (state.isIncludeFiltered) {
242 var tok = filter(stream, state);
243 state.isIncludeFiltered = false;
244 state.restOfLine = 'string';
249 function mixin(stream, state) {
250 if (stream.match(/^mixin\b/)) {
251 state.javaScriptLine = true;
256 function call(stream, state) {
257 if (stream.match(/^\+([-\w]+)/)) {
258 if (!stream.match(/^\( *[-\w]+ *=/, false)) {
259 state.javaScriptArguments = true;
260 state.javaScriptArgumentsDepth = 0;
264 if (stream.match(/^\+#{/, false)) {
266 state.mixinCallAfter = true;
267 return interpolation(stream, state);
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;
281 function conditional(stream, state) {
282 if (stream.match(/^(if|unless|else if|else)\b/)) {
283 state.javaScriptLine = true;
288 function each(stream, state) {
289 if (stream.match(/^(- *)?(each|for)\b/)) {
294 function eachContinued(stream, state) {
296 if (stream.match(/^ in\b/)) {
297 state.javaScriptLine = true;
298 state.isEach = false;
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());
309 function whileStatement(stream, state) {
310 if (stream.match(/^while\b/)) {
311 state.javaScriptLine = true;
316 function tag(stream, state) {
318 if (captures = stream.match(/^(\w(?:[-:\w]*\w)?)\/?/)) {
319 state.lastTag = captures[1].toLowerCase();
320 if (state.lastTag === 'script') {
321 state.scriptType = 'application/javascript';
327 function filter(stream, state) {
328 if (stream.match(/^:([\w\-]+)/)) {
330 if (config && config.innerModes) {
331 innerMode = config.innerModes(stream.current().substring(1));
334 innerMode = stream.current().substring(1);
336 if (typeof innerMode === 'string') {
337 innerMode = CodeMirror.getMode(config, innerMode);
339 setInnerMode(stream, state, innerMode);
344 function code(stream, state) {
345 if (stream.match(/^(!?=|-)/)) {
346 state.javaScriptLine = true;
347 return 'punctuation';
351 function id(stream) {
352 if (stream.match(/^#([\w-]+)/)) {
357 function className(stream) {
358 if (stream.match(/^\.([\w-]+)/)) {
363 function attrs(stream, state) {
364 if (stream.peek() == '(') {
366 state.isAttrs = true;
367 state.attrsNest = [];
368 state.inAttributeName = true;
369 state.attrValue = '';
370 state.attributeIsType = false;
371 return 'punctuation';
375 function attrsContinued(stream, state) {
377 if (ATTRS_NEST[stream.peek()]) {
378 state.attrsNest.push(ATTRS_NEST[stream.peek()]);
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';
386 if (state.inAttributeName && stream.match(/^[^=,\)!]+/)) {
387 if (stream.peek() === '=' || stream.peek() === '!') {
388 state.inAttributeName = false;
389 state.jsState = jsMode.startState();
390 if (state.lastTag === 'script' && stream.current().trim().toLowerCase() === 'type') {
391 state.attributeIsType = true;
393 state.attributeIsType = false;
399 var tok = jsMode.token(stream, state.jsState);
400 if (state.attributeIsType && tok === 'string') {
401 state.scriptType = stream.current().toString();
403 if (state.attrsNest.length === 0 && (tok === 'string' || tok === 'variable' || tok === 'keyword')) {
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);
411 //not the end of an attribute
414 state.attrValue += stream.current();
419 function attributesBlock(stream, state) {
420 if (stream.match(/^&attributes\b/)) {
421 state.javaScriptArguments = true;
422 state.javaScriptArgumentsDepth = 0;
427 function indent(stream) {
428 if (stream.sol() && stream.eatSpace()) {
433 function comment(stream, state) {
434 if (stream.match(/^ *\/\/(-)?([^\n]*)/)) {
435 state.indentOf = stream.indentation();
436 state.indentToken = 'comment';
441 function colon(stream) {
442 if (stream.match(/^: */)) {
447 function text(stream, state) {
448 if (stream.match(/^(?:\| ?| )([^\n]+)/)) {
451 if (stream.match(/^(<[^\n]*)/, false)) {
453 setInnerMode(stream, state, 'htmlmixed');
454 state.innerModeForLine = true;
455 return innerMode(stream, state, true);
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') {
467 setInnerMode(stream, state, innerMode);
472 function fail(stream) {
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();
485 if (mode && mode.name !== 'null') {
486 state.innerMode = mode;
488 state.indentToken = 'string';
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 ? state.innerMode.startState(stream.indentation()) : {};
497 return stream.hideFirstChars(state.indentOf + 2, function () {
498 return state.innerMode.token(stream, state.innerState) || true;
502 return state.indentToken;
504 } else if (stream.sol()) {
505 state.indentOf = Infinity;
506 state.indentToken = null;
507 state.innerMode = null;
508 state.innerState = null;
511 function restOfLine(stream, state) {
513 // if restOfLine was set at end of line, ignore it
514 state.restOfLine = '';
516 if (state.restOfLine) {
518 var tok = state.restOfLine;
519 state.restOfLine = '';
525 function startState() {
528 function copyState(state) {
532 * Get the next token in the stream
534 * @param {Stream} stream
535 * @param {State} state
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)
548 || yieldStatement(stream, state)
549 || doctype(stream, state)
550 || interpolation(stream, state)
551 || caseStatement(stream, state)
552 || when(stream, state)
553 || defaultStatement(stream, state)
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)
569 || className(stream, state)
570 || attrs(stream, state)
571 || attributesBlock(stream, state)
572 || indent(stream, state)
573 || text(stream, state)
574 || comment(stream, state)
575 || colon(stream, state)
576 || dot(stream, state)
577 || fail(stream, state);
579 return tok === true ? null : tok;
582 startState: startState,
583 copyState: copyState,
588 CodeMirror.defineMIME('text/x-jade', 'jade');