jade.js
590 lines
| 15.6 KiB
| application/javascript
|
JavascriptLexer
r1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others | |||
// Distributed under an MIT license: http://codemirror.net/LICENSE | ||||
(function(mod) { | ||||
if (typeof exports == "object" && typeof module == "object") // CommonJS | ||||
mod(require("../../lib/codemirror"), require("../javascript/javascript"), require("../css/css"), require("../htmlmixed/htmlmixed")); | ||||
else if (typeof define == "function" && define.amd) // AMD | ||||
define(["../../lib/codemirror", "../javascript/javascript", "../css/css", "../htmlmixed/htmlmixed"], mod); | ||||
else // Plain browser env | ||||
mod(CodeMirror); | ||||
})(function(CodeMirror) { | ||||
"use strict"; | ||||
CodeMirror.defineMode('jade', function (config) { | ||||
// token types | ||||
var KEYWORD = 'keyword'; | ||||
var DOCTYPE = 'meta'; | ||||
var ID = 'builtin'; | ||||
var CLASS = 'qualifier'; | ||||
var ATTRS_NEST = { | ||||
'{': '}', | ||||
'(': ')', | ||||
'[': ']' | ||||
}; | ||||
var jsMode = CodeMirror.getMode(config, 'javascript'); | ||||
function State() { | ||||
this.javaScriptLine = false; | ||||
this.javaScriptLineExcludesColon = false; | ||||
this.javaScriptArguments = false; | ||||
this.javaScriptArgumentsDepth = 0; | ||||
this.isInterpolating = false; | ||||
this.interpolationNesting = 0; | ||||
this.jsState = jsMode.startState(); | ||||
this.restOfLine = ''; | ||||
this.isIncludeFiltered = false; | ||||
this.isEach = false; | ||||
this.lastTag = ''; | ||||
this.scriptType = ''; | ||||
// Attributes Mode | ||||
this.isAttrs = false; | ||||
this.attrsNest = []; | ||||
this.inAttributeName = true; | ||||
this.attributeIsType = false; | ||||
this.attrValue = ''; | ||||
// Indented Mode | ||||
this.indentOf = Infinity; | ||||
this.indentToken = ''; | ||||
this.innerMode = null; | ||||
this.innerState = null; | ||||
this.innerModeForLine = false; | ||||
} | ||||
/** | ||||
* Safely copy a state | ||||
* | ||||
* @return {State} | ||||
*/ | ||||
State.prototype.copy = function () { | ||||
var res = new State(); | ||||
res.javaScriptLine = this.javaScriptLine; | ||||
res.javaScriptLineExcludesColon = this.javaScriptLineExcludesColon; | ||||
res.javaScriptArguments = this.javaScriptArguments; | ||||
res.javaScriptArgumentsDepth = this.javaScriptArgumentsDepth; | ||||
res.isInterpolating = this.isInterpolating; | ||||
res.interpolationNesting = this.intpolationNesting; | ||||
res.jsState = CodeMirror.copyState(jsMode, this.jsState); | ||||
res.innerMode = this.innerMode; | ||||
if (this.innerMode && this.innerState) { | ||||
res.innerState = CodeMirror.copyState(this.innerMode, this.innerState); | ||||
} | ||||
res.restOfLine = this.restOfLine; | ||||
res.isIncludeFiltered = this.isIncludeFiltered; | ||||
res.isEach = this.isEach; | ||||
res.lastTag = this.lastTag; | ||||
res.scriptType = this.scriptType; | ||||
res.isAttrs = this.isAttrs; | ||||
res.attrsNest = this.attrsNest.slice(); | ||||
res.inAttributeName = this.inAttributeName; | ||||
res.attributeIsType = this.attributeIsType; | ||||
res.attrValue = this.attrValue; | ||||
res.indentOf = this.indentOf; | ||||
res.indentToken = this.indentToken; | ||||
res.innerModeForLine = this.innerModeForLine; | ||||
return res; | ||||
}; | ||||
function javaScript(stream, state) { | ||||
if (stream.sol()) { | ||||
// if javaScriptLine was set at end of line, ignore it | ||||
state.javaScriptLine = false; | ||||
state.javaScriptLineExcludesColon = false; | ||||
} | ||||
if (state.javaScriptLine) { | ||||
if (state.javaScriptLineExcludesColon && stream.peek() === ':') { | ||||
state.javaScriptLine = false; | ||||
state.javaScriptLineExcludesColon = false; | ||||
return; | ||||
} | ||||
var tok = jsMode.token(stream, state.jsState); | ||||
if (stream.eol()) state.javaScriptLine = false; | ||||
return tok || true; | ||||
} | ||||
} | ||||
function javaScriptArguments(stream, state) { | ||||
if (state.javaScriptArguments) { | ||||
if (state.javaScriptArgumentsDepth === 0 && stream.peek() !== '(') { | ||||
state.javaScriptArguments = false; | ||||
return; | ||||
} | ||||
if (stream.peek() === '(') { | ||||
state.javaScriptArgumentsDepth++; | ||||
} else if (stream.peek() === ')') { | ||||
state.javaScriptArgumentsDepth--; | ||||
} | ||||
if (state.javaScriptArgumentsDepth === 0) { | ||||
state.javaScriptArguments = false; | ||||
return; | ||||
} | ||||
var tok = jsMode.token(stream, state.jsState); | ||||
return tok || true; | ||||
} | ||||
} | ||||
function yieldStatement(stream) { | ||||
if (stream.match(/^yield\b/)) { | ||||
return 'keyword'; | ||||
} | ||||
} | ||||
function doctype(stream) { | ||||
if (stream.match(/^(?:doctype) *([^\n]+)?/)) { | ||||
return DOCTYPE; | ||||
} | ||||
} | ||||
function interpolation(stream, state) { | ||||
if (stream.match('#{')) { | ||||
state.isInterpolating = true; | ||||
state.interpolationNesting = 0; | ||||
return 'punctuation'; | ||||
} | ||||
} | ||||
function interpolationContinued(stream, state) { | ||||
if (state.isInterpolating) { | ||||
if (stream.peek() === '}') { | ||||
state.interpolationNesting--; | ||||
if (state.interpolationNesting < 0) { | ||||
stream.next(); | ||||
state.isInterpolating = false; | ||||
return 'puncutation'; | ||||
} | ||||
} else if (stream.peek() === '{') { | ||||
state.interpolationNesting++; | ||||
} | ||||
return jsMode.token(stream, state.jsState) || true; | ||||
} | ||||
} | ||||
function caseStatement(stream, state) { | ||||
if (stream.match(/^case\b/)) { | ||||
state.javaScriptLine = true; | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function when(stream, state) { | ||||
if (stream.match(/^when\b/)) { | ||||
state.javaScriptLine = true; | ||||
state.javaScriptLineExcludesColon = true; | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function defaultStatement(stream) { | ||||
if (stream.match(/^default\b/)) { | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function extendsStatement(stream, state) { | ||||
if (stream.match(/^extends?\b/)) { | ||||
state.restOfLine = 'string'; | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function append(stream, state) { | ||||
if (stream.match(/^append\b/)) { | ||||
state.restOfLine = 'variable'; | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function prepend(stream, state) { | ||||
if (stream.match(/^prepend\b/)) { | ||||
state.restOfLine = 'variable'; | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function block(stream, state) { | ||||
if (stream.match(/^block\b *(?:(prepend|append)\b)?/)) { | ||||
state.restOfLine = 'variable'; | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function include(stream, state) { | ||||
if (stream.match(/^include\b/)) { | ||||
state.restOfLine = 'string'; | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function includeFiltered(stream, state) { | ||||
if (stream.match(/^include:([a-zA-Z0-9\-]+)/, false) && stream.match('include')) { | ||||
state.isIncludeFiltered = true; | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function includeFilteredContinued(stream, state) { | ||||
if (state.isIncludeFiltered) { | ||||
var tok = filter(stream, state); | ||||
state.isIncludeFiltered = false; | ||||
state.restOfLine = 'string'; | ||||
return tok; | ||||
} | ||||
} | ||||
function mixin(stream, state) { | ||||
if (stream.match(/^mixin\b/)) { | ||||
state.javaScriptLine = true; | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function call(stream, state) { | ||||
if (stream.match(/^\+([-\w]+)/)) { | ||||
if (!stream.match(/^\( *[-\w]+ *=/, false)) { | ||||
state.javaScriptArguments = true; | ||||
state.javaScriptArgumentsDepth = 0; | ||||
} | ||||
return 'variable'; | ||||
} | ||||
if (stream.match(/^\+#{/, false)) { | ||||
stream.next(); | ||||
state.mixinCallAfter = true; | ||||
return interpolation(stream, state); | ||||
} | ||||
} | ||||
function callArguments(stream, state) { | ||||
if (state.mixinCallAfter) { | ||||
state.mixinCallAfter = false; | ||||
if (!stream.match(/^\( *[-\w]+ *=/, false)) { | ||||
state.javaScriptArguments = true; | ||||
state.javaScriptArgumentsDepth = 0; | ||||
} | ||||
return true; | ||||
} | ||||
} | ||||
function conditional(stream, state) { | ||||
if (stream.match(/^(if|unless|else if|else)\b/)) { | ||||
state.javaScriptLine = true; | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function each(stream, state) { | ||||
if (stream.match(/^(- *)?(each|for)\b/)) { | ||||
state.isEach = true; | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function eachContinued(stream, state) { | ||||
if (state.isEach) { | ||||
if (stream.match(/^ in\b/)) { | ||||
state.javaScriptLine = true; | ||||
state.isEach = false; | ||||
return KEYWORD; | ||||
} else if (stream.sol() || stream.eol()) { | ||||
state.isEach = false; | ||||
} else if (stream.next()) { | ||||
while (!stream.match(/^ in\b/, false) && stream.next()); | ||||
return 'variable'; | ||||
} | ||||
} | ||||
} | ||||
function whileStatement(stream, state) { | ||||
if (stream.match(/^while\b/)) { | ||||
state.javaScriptLine = true; | ||||
return KEYWORD; | ||||
} | ||||
} | ||||
function tag(stream, state) { | ||||
var captures; | ||||
if (captures = stream.match(/^(\w(?:[-:\w]*\w)?)\/?/)) { | ||||
state.lastTag = captures[1].toLowerCase(); | ||||
if (state.lastTag === 'script') { | ||||
state.scriptType = 'application/javascript'; | ||||
} | ||||
return 'tag'; | ||||
} | ||||
} | ||||
function filter(stream, state) { | ||||
if (stream.match(/^:([\w\-]+)/)) { | ||||
var innerMode; | ||||
if (config && config.innerModes) { | ||||
innerMode = config.innerModes(stream.current().substring(1)); | ||||
} | ||||
if (!innerMode) { | ||||
innerMode = stream.current().substring(1); | ||||
} | ||||
if (typeof innerMode === 'string') { | ||||
innerMode = CodeMirror.getMode(config, innerMode); | ||||
} | ||||
setInnerMode(stream, state, innerMode); | ||||
return 'atom'; | ||||
} | ||||
} | ||||
function code(stream, state) { | ||||
if (stream.match(/^(!?=|-)/)) { | ||||
state.javaScriptLine = true; | ||||
return 'punctuation'; | ||||
} | ||||
} | ||||
function id(stream) { | ||||
if (stream.match(/^#([\w-]+)/)) { | ||||
return ID; | ||||
} | ||||
} | ||||
function className(stream) { | ||||
if (stream.match(/^\.([\w-]+)/)) { | ||||
return CLASS; | ||||
} | ||||
} | ||||
function attrs(stream, state) { | ||||
if (stream.peek() == '(') { | ||||
stream.next(); | ||||
state.isAttrs = true; | ||||
state.attrsNest = []; | ||||
state.inAttributeName = true; | ||||
state.attrValue = ''; | ||||
state.attributeIsType = false; | ||||
return 'punctuation'; | ||||
} | ||||
} | ||||
function attrsContinued(stream, state) { | ||||
if (state.isAttrs) { | ||||
if (ATTRS_NEST[stream.peek()]) { | ||||
state.attrsNest.push(ATTRS_NEST[stream.peek()]); | ||||
} | ||||
if (state.attrsNest[state.attrsNest.length - 1] === stream.peek()) { | ||||
state.attrsNest.pop(); | ||||
} else if (stream.eat(')')) { | ||||
state.isAttrs = false; | ||||
return 'punctuation'; | ||||
} | ||||
if (state.inAttributeName && stream.match(/^[^=,\)!]+/)) { | ||||
if (stream.peek() === '=' || stream.peek() === '!') { | ||||
state.inAttributeName = false; | ||||
state.jsState = jsMode.startState(); | ||||
if (state.lastTag === 'script' && stream.current().trim().toLowerCase() === 'type') { | ||||
state.attributeIsType = true; | ||||
} else { | ||||
state.attributeIsType = false; | ||||
} | ||||
} | ||||
return 'attribute'; | ||||
} | ||||
var tok = jsMode.token(stream, state.jsState); | ||||
if (state.attributeIsType && tok === 'string') { | ||||
state.scriptType = stream.current().toString(); | ||||
} | ||||
if (state.attrsNest.length === 0 && (tok === 'string' || tok === 'variable' || tok === 'keyword')) { | ||||
try { | ||||
Function('', 'var x ' + state.attrValue.replace(/,\s*$/, '').replace(/^!/, '')); | ||||
state.inAttributeName = true; | ||||
state.attrValue = ''; | ||||
stream.backUp(stream.current().length); | ||||
return attrsContinued(stream, state); | ||||
} catch (ex) { | ||||
//not the end of an attribute | ||||
} | ||||
} | ||||
state.attrValue += stream.current(); | ||||
return tok || true; | ||||
} | ||||
} | ||||
function attributesBlock(stream, state) { | ||||
if (stream.match(/^&attributes\b/)) { | ||||
state.javaScriptArguments = true; | ||||
state.javaScriptArgumentsDepth = 0; | ||||
return 'keyword'; | ||||
} | ||||
} | ||||
function indent(stream) { | ||||
if (stream.sol() && stream.eatSpace()) { | ||||
return 'indent'; | ||||
} | ||||
} | ||||
function comment(stream, state) { | ||||
if (stream.match(/^ *\/\/(-)?([^\n]*)/)) { | ||||
state.indentOf = stream.indentation(); | ||||
state.indentToken = 'comment'; | ||||
return 'comment'; | ||||
} | ||||
} | ||||
function colon(stream) { | ||||
if (stream.match(/^: */)) { | ||||
return 'colon'; | ||||
} | ||||
} | ||||
function text(stream, state) { | ||||
if (stream.match(/^(?:\| ?| )([^\n]+)/)) { | ||||
return 'string'; | ||||
} | ||||
if (stream.match(/^(<[^\n]*)/, false)) { | ||||
// html string | ||||
setInnerMode(stream, state, 'htmlmixed'); | ||||
state.innerModeForLine = true; | ||||
return innerMode(stream, state, true); | ||||
} | ||||
} | ||||
function dot(stream, state) { | ||||
if (stream.eat('.')) { | ||||
var innerMode = null; | ||||
if (state.lastTag === 'script' && state.scriptType.toLowerCase().indexOf('javascript') != -1) { | ||||
innerMode = state.scriptType.toLowerCase().replace(/"|'/g, ''); | ||||
} else if (state.lastTag === 'style') { | ||||
innerMode = 'css'; | ||||
} | ||||
setInnerMode(stream, state, innerMode); | ||||
return 'dot'; | ||||
} | ||||
} | ||||
function fail(stream) { | ||||
stream.next(); | ||||
return null; | ||||
} | ||||
function setInnerMode(stream, state, mode) { | ||||
mode = CodeMirror.mimeModes[mode] || mode; | ||||
mode = config.innerModes ? config.innerModes(mode) || mode : mode; | ||||
mode = CodeMirror.mimeModes[mode] || mode; | ||||
mode = CodeMirror.getMode(config, mode); | ||||
state.indentOf = stream.indentation(); | ||||
if (mode && mode.name !== 'null') { | ||||
state.innerMode = mode; | ||||
} else { | ||||
state.indentToken = 'string'; | ||||
} | ||||
} | ||||
function innerMode(stream, state, force) { | ||||
if (stream.indentation() > state.indentOf || (state.innerModeForLine && !stream.sol()) || force) { | ||||
if (state.innerMode) { | ||||
if (!state.innerState) { | ||||
state.innerState = state.innerMode.startState ? state.innerMode.startState(stream.indentation()) : {}; | ||||
} | ||||
return stream.hideFirstChars(state.indentOf + 2, function () { | ||||
return state.innerMode.token(stream, state.innerState) || true; | ||||
}); | ||||
} else { | ||||
stream.skipToEnd(); | ||||
return state.indentToken; | ||||
} | ||||
} else if (stream.sol()) { | ||||
state.indentOf = Infinity; | ||||
state.indentToken = null; | ||||
state.innerMode = null; | ||||
state.innerState = null; | ||||
} | ||||
} | ||||
function restOfLine(stream, state) { | ||||
if (stream.sol()) { | ||||
// if restOfLine was set at end of line, ignore it | ||||
state.restOfLine = ''; | ||||
} | ||||
if (state.restOfLine) { | ||||
stream.skipToEnd(); | ||||
var tok = state.restOfLine; | ||||
state.restOfLine = ''; | ||||
return tok; | ||||
} | ||||
} | ||||
function startState() { | ||||
return new State(); | ||||
} | ||||
function copyState(state) { | ||||
return state.copy(); | ||||
} | ||||
/** | ||||
* Get the next token in the stream | ||||
* | ||||
* @param {Stream} stream | ||||
* @param {State} state | ||||
*/ | ||||
function nextToken(stream, state) { | ||||
var tok = innerMode(stream, state) | ||||
|| restOfLine(stream, state) | ||||
|| interpolationContinued(stream, state) | ||||
|| includeFilteredContinued(stream, state) | ||||
|| eachContinued(stream, state) | ||||
|| attrsContinued(stream, state) | ||||
|| javaScript(stream, state) | ||||
|| javaScriptArguments(stream, state) | ||||
|| callArguments(stream, state) | ||||
|| yieldStatement(stream, state) | ||||
|| doctype(stream, state) | ||||
|| interpolation(stream, state) | ||||
|| caseStatement(stream, state) | ||||
|| when(stream, state) | ||||
|| defaultStatement(stream, state) | ||||
|| extendsStatement(stream, state) | ||||
|| append(stream, state) | ||||
|| prepend(stream, state) | ||||
|| block(stream, state) | ||||
|| include(stream, state) | ||||
|| includeFiltered(stream, state) | ||||
|| mixin(stream, state) | ||||
|| call(stream, state) | ||||
|| conditional(stream, state) | ||||
|| each(stream, state) | ||||
|| whileStatement(stream, state) | ||||
|| tag(stream, state) | ||||
|| filter(stream, state) | ||||
|| code(stream, state) | ||||
|| id(stream, state) | ||||
|| className(stream, state) | ||||
|| attrs(stream, state) | ||||
|| attributesBlock(stream, state) | ||||
|| indent(stream, state) | ||||
|| text(stream, state) | ||||
|| comment(stream, state) | ||||
|| colon(stream, state) | ||||
|| dot(stream, state) | ||||
|| fail(stream, state); | ||||
return tok === true ? null : tok; | ||||
} | ||||
return { | ||||
startState: startState, | ||||
copyState: copyState, | ||||
token: nextToken | ||||
}; | ||||
}); | ||||
CodeMirror.defineMIME('text/x-jade', 'jade'); | ||||
}); | ||||