github.com/elliott5/community@v0.14.1-0.20160709191136-823126fb026a/app/public/codemirror/mode/slim/slim.js (about)

     1  // CodeMirror, copyright (c) by Marijn Haverbeke and others
     2  // Distributed under an MIT license: http://codemirror.net/LICENSE
     3  
     4  // Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh
     5  
     6  (function(mod) {
     7    if (typeof exports == "object" && typeof module == "object") // CommonJS
     8      mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../ruby/ruby"));
     9    else if (typeof define == "function" && define.amd) // AMD
    10      define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../ruby/ruby"], mod);
    11    else // Plain browser env
    12      mod(CodeMirror);
    13  })(function(CodeMirror) {
    14  "use strict";
    15  
    16    CodeMirror.defineMode("slim", function(config) {
    17      var htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"});
    18      var rubyMode = CodeMirror.getMode(config, "ruby");
    19      var modes = { html: htmlMode, ruby: rubyMode };
    20      var embedded = {
    21        ruby: "ruby",
    22        javascript: "javascript",
    23        css: "text/css",
    24        sass: "text/x-sass",
    25        scss: "text/x-scss",
    26        less: "text/x-less",
    27        styl: "text/x-styl", // no highlighting so far
    28        coffee: "coffeescript",
    29        asciidoc: "text/x-asciidoc",
    30        markdown: "text/x-markdown",
    31        textile: "text/x-textile", // no highlighting so far
    32        creole: "text/x-creole", // no highlighting so far
    33        wiki: "text/x-wiki", // no highlighting so far
    34        mediawiki: "text/x-mediawiki", // no highlighting so far
    35        rdoc: "text/x-rdoc", // no highlighting so far
    36        builder: "text/x-builder", // no highlighting so far
    37        nokogiri: "text/x-nokogiri", // no highlighting so far
    38        erb: "application/x-erb"
    39      };
    40      var embeddedRegexp = function(map){
    41        var arr = [];
    42        for(var key in map) arr.push(key);
    43        return new RegExp("^("+arr.join('|')+"):");
    44      }(embedded);
    45  
    46      var styleMap = {
    47        "commentLine": "comment",
    48        "slimSwitch": "operator special",
    49        "slimTag": "tag",
    50        "slimId": "attribute def",
    51        "slimClass": "attribute qualifier",
    52        "slimAttribute": "attribute",
    53        "slimSubmode": "keyword special",
    54        "closeAttributeTag": null,
    55        "slimDoctype": null,
    56        "lineContinuation": null
    57      };
    58      var closing = {
    59        "{": "}",
    60        "[": "]",
    61        "(": ")"
    62      };
    63  
    64      var nameStartChar = "_a-zA-Z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD";
    65      var nameChar = nameStartChar + "\\-0-9\xB7\u0300-\u036F\u203F-\u2040";
    66      var nameRegexp = new RegExp("^[:"+nameStartChar+"](?::["+nameChar+"]|["+nameChar+"]*)");
    67      var attributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*(?=\\s*=)");
    68      var wrappedAttributeNameRegexp = new RegExp("^[:"+nameStartChar+"][:\\."+nameChar+"]*");
    69      var classNameRegexp = /^\.-?[_a-zA-Z]+[\w\-]*/;
    70      var classIdRegexp = /^#[_a-zA-Z]+[\w\-]*/;
    71  
    72      function backup(pos, tokenize, style) {
    73        var restore = function(stream, state) {
    74          state.tokenize = tokenize;
    75          if (stream.pos < pos) {
    76            stream.pos = pos;
    77            return style;
    78          }
    79          return state.tokenize(stream, state);
    80        };
    81        return function(stream, state) {
    82          state.tokenize = restore;
    83          return tokenize(stream, state);
    84        };
    85      }
    86  
    87      function maybeBackup(stream, state, pat, offset, style) {
    88        var cur = stream.current();
    89        var idx = cur.search(pat);
    90        if (idx > -1) {
    91          state.tokenize = backup(stream.pos, state.tokenize, style);
    92          stream.backUp(cur.length - idx - offset);
    93        }
    94        return style;
    95      }
    96  
    97      function continueLine(state, column) {
    98        state.stack = {
    99          parent: state.stack,
   100          style: "continuation",
   101          indented: column,
   102          tokenize: state.line
   103        };
   104        state.line = state.tokenize;
   105      }
   106      function finishContinue(state) {
   107        if (state.line == state.tokenize) {
   108          state.line = state.stack.tokenize;
   109          state.stack = state.stack.parent;
   110        }
   111      }
   112  
   113      function lineContinuable(column, tokenize) {
   114        return function(stream, state) {
   115          finishContinue(state);
   116          if (stream.match(/^\\$/)) {
   117            continueLine(state, column);
   118            return "lineContinuation";
   119          }
   120          var style = tokenize(stream, state);
   121          if (stream.eol() && stream.current().match(/(?:^|[^\\])(?:\\\\)*\\$/)) {
   122            stream.backUp(1);
   123          }
   124          return style;
   125        };
   126      }
   127      function commaContinuable(column, tokenize) {
   128        return function(stream, state) {
   129          finishContinue(state);
   130          var style = tokenize(stream, state);
   131          if (stream.eol() && stream.current().match(/,$/)) {
   132            continueLine(state, column);
   133          }
   134          return style;
   135        };
   136      }
   137  
   138      function rubyInQuote(endQuote, tokenize) {
   139        // TODO: add multi line support
   140        return function(stream, state) {
   141          var ch = stream.peek();
   142          if (ch == endQuote && state.rubyState.tokenize.length == 1) {
   143            // step out of ruby context as it seems to complete processing all the braces
   144            stream.next();
   145            state.tokenize = tokenize;
   146            return "closeAttributeTag";
   147          } else {
   148            return ruby(stream, state);
   149          }
   150        };
   151      }
   152      function startRubySplat(tokenize) {
   153        var rubyState;
   154        var runSplat = function(stream, state) {
   155          if (state.rubyState.tokenize.length == 1 && !state.rubyState.context.prev) {
   156            stream.backUp(1);
   157            if (stream.eatSpace()) {
   158              state.rubyState = rubyState;
   159              state.tokenize = tokenize;
   160              return tokenize(stream, state);
   161            }
   162            stream.next();
   163          }
   164          return ruby(stream, state);
   165        };
   166        return function(stream, state) {
   167          rubyState = state.rubyState;
   168          state.rubyState = rubyMode.startState();
   169          state.tokenize = runSplat;
   170          return ruby(stream, state);
   171        };
   172      }
   173  
   174      function ruby(stream, state) {
   175        return rubyMode.token(stream, state.rubyState);
   176      }
   177  
   178      function htmlLine(stream, state) {
   179        if (stream.match(/^\\$/)) {
   180          return "lineContinuation";
   181        }
   182        return html(stream, state);
   183      }
   184      function html(stream, state) {
   185        if (stream.match(/^#\{/)) {
   186          state.tokenize = rubyInQuote("}", state.tokenize);
   187          return null;
   188        }
   189        return maybeBackup(stream, state, /[^\\]#\{/, 1, htmlMode.token(stream, state.htmlState));
   190      }
   191  
   192      function startHtmlLine(lastTokenize) {
   193        return function(stream, state) {
   194          var style = htmlLine(stream, state);
   195          if (stream.eol()) state.tokenize = lastTokenize;
   196          return style;
   197        };
   198      }
   199  
   200      function startHtmlMode(stream, state, offset) {
   201        state.stack = {
   202          parent: state.stack,
   203          style: "html",
   204          indented: stream.column() + offset, // pipe + space
   205          tokenize: state.line
   206        };
   207        state.line = state.tokenize = html;
   208        return null;
   209      }
   210  
   211      function comment(stream, state) {
   212        stream.skipToEnd();
   213        return state.stack.style;
   214      }
   215  
   216      function commentMode(stream, state) {
   217        state.stack = {
   218          parent: state.stack,
   219          style: "comment",
   220          indented: state.indented + 1,
   221          tokenize: state.line
   222        };
   223        state.line = comment;
   224        return comment(stream, state);
   225      }
   226  
   227      function attributeWrapper(stream, state) {
   228        if (stream.eat(state.stack.endQuote)) {
   229          state.line = state.stack.line;
   230          state.tokenize = state.stack.tokenize;
   231          state.stack = state.stack.parent;
   232          return null;
   233        }
   234        if (stream.match(wrappedAttributeNameRegexp)) {
   235          state.tokenize = attributeWrapperAssign;
   236          return "slimAttribute";
   237        }
   238        stream.next();
   239        return null;
   240      }
   241      function attributeWrapperAssign(stream, state) {
   242        if (stream.match(/^==?/)) {
   243          state.tokenize = attributeWrapperValue;
   244          return null;
   245        }
   246        return attributeWrapper(stream, state);
   247      }
   248      function attributeWrapperValue(stream, state) {
   249        var ch = stream.peek();
   250        if (ch == '"' || ch == "\'") {
   251          state.tokenize = readQuoted(ch, "string", true, false, attributeWrapper);
   252          stream.next();
   253          return state.tokenize(stream, state);
   254        }
   255        if (ch == '[') {
   256          return startRubySplat(attributeWrapper)(stream, state);
   257        }
   258        if (stream.match(/^(true|false|nil)\b/)) {
   259          state.tokenize = attributeWrapper;
   260          return "keyword";
   261        }
   262        return startRubySplat(attributeWrapper)(stream, state);
   263      }
   264  
   265      function startAttributeWrapperMode(state, endQuote, tokenize) {
   266        state.stack = {
   267          parent: state.stack,
   268          style: "wrapper",
   269          indented: state.indented + 1,
   270          tokenize: tokenize,
   271          line: state.line,
   272          endQuote: endQuote
   273        };
   274        state.line = state.tokenize = attributeWrapper;
   275        return null;
   276      }
   277  
   278      function sub(stream, state) {
   279        if (stream.match(/^#\{/)) {
   280          state.tokenize = rubyInQuote("}", state.tokenize);
   281          return null;
   282        }
   283        var subStream = new CodeMirror.StringStream(stream.string.slice(state.stack.indented), stream.tabSize);
   284        subStream.pos = stream.pos - state.stack.indented;
   285        subStream.start = stream.start - state.stack.indented;
   286        subStream.lastColumnPos = stream.lastColumnPos - state.stack.indented;
   287        subStream.lastColumnValue = stream.lastColumnValue - state.stack.indented;
   288        var style = state.subMode.token(subStream, state.subState);
   289        stream.pos = subStream.pos + state.stack.indented;
   290        return style;
   291      }
   292      function firstSub(stream, state) {
   293        state.stack.indented = stream.column();
   294        state.line = state.tokenize = sub;
   295        return state.tokenize(stream, state);
   296      }
   297  
   298      function createMode(mode) {
   299        var query = embedded[mode];
   300        var spec = CodeMirror.mimeModes[query];
   301        if (spec) {
   302          return CodeMirror.getMode(config, spec);
   303        }
   304        var factory = CodeMirror.modes[query];
   305        if (factory) {
   306          return factory(config, {name: query});
   307        }
   308        return CodeMirror.getMode(config, "null");
   309      }
   310  
   311      function getMode(mode) {
   312        if (!modes.hasOwnProperty(mode)) {
   313          return modes[mode] = createMode(mode);
   314        }
   315        return modes[mode];
   316      }
   317  
   318      function startSubMode(mode, state) {
   319        var subMode = getMode(mode);
   320        var subState = subMode.startState && subMode.startState();
   321  
   322        state.subMode = subMode;
   323        state.subState = subState;
   324  
   325        state.stack = {
   326          parent: state.stack,
   327          style: "sub",
   328          indented: state.indented + 1,
   329          tokenize: state.line
   330        };
   331        state.line = state.tokenize = firstSub;
   332        return "slimSubmode";
   333      }
   334  
   335      function doctypeLine(stream, _state) {
   336        stream.skipToEnd();
   337        return "slimDoctype";
   338      }
   339  
   340      function startLine(stream, state) {
   341        var ch = stream.peek();
   342        if (ch == '<') {
   343          return (state.tokenize = startHtmlLine(state.tokenize))(stream, state);
   344        }
   345        if (stream.match(/^[|']/)) {
   346          return startHtmlMode(stream, state, 1);
   347        }
   348        if (stream.match(/^\/(!|\[\w+])?/)) {
   349          return commentMode(stream, state);
   350        }
   351        if (stream.match(/^(-|==?[<>]?)/)) {
   352          state.tokenize = lineContinuable(stream.column(), commaContinuable(stream.column(), ruby));
   353          return "slimSwitch";
   354        }
   355        if (stream.match(/^doctype\b/)) {
   356          state.tokenize = doctypeLine;
   357          return "keyword";
   358        }
   359  
   360        var m = stream.match(embeddedRegexp);
   361        if (m) {
   362          return startSubMode(m[1], state);
   363        }
   364  
   365        return slimTag(stream, state);
   366      }
   367  
   368      function slim(stream, state) {
   369        if (state.startOfLine) {
   370          return startLine(stream, state);
   371        }
   372        return slimTag(stream, state);
   373      }
   374  
   375      function slimTag(stream, state) {
   376        if (stream.eat('*')) {
   377          state.tokenize = startRubySplat(slimTagExtras);
   378          return null;
   379        }
   380        if (stream.match(nameRegexp)) {
   381          state.tokenize = slimTagExtras;
   382          return "slimTag";
   383        }
   384        return slimClass(stream, state);
   385      }
   386      function slimTagExtras(stream, state) {
   387        if (stream.match(/^(<>?|><?)/)) {
   388          state.tokenize = slimClass;
   389          return null;
   390        }
   391        return slimClass(stream, state);
   392      }
   393      function slimClass(stream, state) {
   394        if (stream.match(classIdRegexp)) {
   395          state.tokenize = slimClass;
   396          return "slimId";
   397        }
   398        if (stream.match(classNameRegexp)) {
   399          state.tokenize = slimClass;
   400          return "slimClass";
   401        }
   402        return slimAttribute(stream, state);
   403      }
   404      function slimAttribute(stream, state) {
   405        if (stream.match(/^([\[\{\(])/)) {
   406          return startAttributeWrapperMode(state, closing[RegExp.$1], slimAttribute);
   407        }
   408        if (stream.match(attributeNameRegexp)) {
   409          state.tokenize = slimAttributeAssign;
   410          return "slimAttribute";
   411        }
   412        if (stream.peek() == '*') {
   413          stream.next();
   414          state.tokenize = startRubySplat(slimContent);
   415          return null;
   416        }
   417        return slimContent(stream, state);
   418      }
   419      function slimAttributeAssign(stream, state) {
   420        if (stream.match(/^==?/)) {
   421          state.tokenize = slimAttributeValue;
   422          return null;
   423        }
   424        // should never happen, because of forward lookup
   425        return slimAttribute(stream, state);
   426      }
   427  
   428      function slimAttributeValue(stream, state) {
   429        var ch = stream.peek();
   430        if (ch == '"' || ch == "\'") {
   431          state.tokenize = readQuoted(ch, "string", true, false, slimAttribute);
   432          stream.next();
   433          return state.tokenize(stream, state);
   434        }
   435        if (ch == '[') {
   436          return startRubySplat(slimAttribute)(stream, state);
   437        }
   438        if (ch == ':') {
   439          return startRubySplat(slimAttributeSymbols)(stream, state);
   440        }
   441        if (stream.match(/^(true|false|nil)\b/)) {
   442          state.tokenize = slimAttribute;
   443          return "keyword";
   444        }
   445        return startRubySplat(slimAttribute)(stream, state);
   446      }
   447      function slimAttributeSymbols(stream, state) {
   448        stream.backUp(1);
   449        if (stream.match(/^[^\s],(?=:)/)) {
   450          state.tokenize = startRubySplat(slimAttributeSymbols);
   451          return null;
   452        }
   453        stream.next();
   454        return slimAttribute(stream, state);
   455      }
   456      function readQuoted(quote, style, embed, unescaped, nextTokenize) {
   457        return function(stream, state) {
   458          finishContinue(state);
   459          var fresh = stream.current().length == 0;
   460          if (stream.match(/^\\$/, fresh)) {
   461            if (!fresh) return style;
   462            continueLine(state, state.indented);
   463            return "lineContinuation";
   464          }
   465          if (stream.match(/^#\{/, fresh)) {
   466            if (!fresh) return style;
   467            state.tokenize = rubyInQuote("}", state.tokenize);
   468            return null;
   469          }
   470          var escaped = false, ch;
   471          while ((ch = stream.next()) != null) {
   472            if (ch == quote && (unescaped || !escaped)) {
   473              state.tokenize = nextTokenize;
   474              break;
   475            }
   476            if (embed && ch == "#" && !escaped) {
   477              if (stream.eat("{")) {
   478                stream.backUp(2);
   479                break;
   480              }
   481            }
   482            escaped = !escaped && ch == "\\";
   483          }
   484          if (stream.eol() && escaped) {
   485            stream.backUp(1);
   486          }
   487          return style;
   488        };
   489      }
   490      function slimContent(stream, state) {
   491        if (stream.match(/^==?/)) {
   492          state.tokenize = ruby;
   493          return "slimSwitch";
   494        }
   495        if (stream.match(/^\/$/)) { // tag close hint
   496          state.tokenize = slim;
   497          return null;
   498        }
   499        if (stream.match(/^:/)) { // inline tag
   500          state.tokenize = slimTag;
   501          return "slimSwitch";
   502        }
   503        startHtmlMode(stream, state, 0);
   504        return state.tokenize(stream, state);
   505      }
   506  
   507      var mode = {
   508        // default to html mode
   509        startState: function() {
   510          var htmlState = htmlMode.startState();
   511          var rubyState = rubyMode.startState();
   512          return {
   513            htmlState: htmlState,
   514            rubyState: rubyState,
   515            stack: null,
   516            last: null,
   517            tokenize: slim,
   518            line: slim,
   519            indented: 0
   520          };
   521        },
   522  
   523        copyState: function(state) {
   524          return {
   525            htmlState : CodeMirror.copyState(htmlMode, state.htmlState),
   526            rubyState: CodeMirror.copyState(rubyMode, state.rubyState),
   527            subMode: state.subMode,
   528            subState: state.subMode && CodeMirror.copyState(state.subMode, state.subState),
   529            stack: state.stack,
   530            last: state.last,
   531            tokenize: state.tokenize,
   532            line: state.line
   533          };
   534        },
   535  
   536        token: function(stream, state) {
   537          if (stream.sol()) {
   538            state.indented = stream.indentation();
   539            state.startOfLine = true;
   540            state.tokenize = state.line;
   541            while (state.stack && state.stack.indented > state.indented && state.last != "slimSubmode") {
   542              state.line = state.tokenize = state.stack.tokenize;
   543              state.stack = state.stack.parent;
   544              state.subMode = null;
   545              state.subState = null;
   546            }
   547          }
   548          if (stream.eatSpace()) return null;
   549          var style = state.tokenize(stream, state);
   550          state.startOfLine = false;
   551          if (style) state.last = style;
   552          return styleMap.hasOwnProperty(style) ? styleMap[style] : style;
   553        },
   554  
   555        blankLine: function(state) {
   556          if (state.subMode && state.subMode.blankLine) {
   557            return state.subMode.blankLine(state.subState);
   558          }
   559        },
   560  
   561        innerMode: function(state) {
   562          if (state.subMode) return {state: state.subState, mode: state.subMode};
   563          return {state: state, mode: mode};
   564        }
   565  
   566        //indent: function(state) {
   567        //  return state.indented;
   568        //}
   569      };
   570      return mode;
   571    }, "htmlmixed", "ruby");
   572  
   573    CodeMirror.defineMIME("text/x-slim", "slim");
   574    CodeMirror.defineMIME("application/x-slim", "slim");
   575  });