|
- // CodeMirror, copyright (c) by Marijn Haverbeke and others
- // Distributed under an MIT license: https://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("pug", 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 = CodeMirror.startState(jsMode);
-
- 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.interpolationNesting;
-
- 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 'punctuation';
- }
- } 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 = CodeMirror.startState(jsMode);
- 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 ? CodeMirror.startState(state.innerMode, 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
- };
- }, 'javascript', 'css', 'htmlmixed');
-
- CodeMirror.defineMIME('text/x-pug', 'pug');
- CodeMirror.defineMIME('text/x-jade', 'pug');
-
- });
|