openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

slim.js (18026B)


      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 = CodeMirror.startState(rubyMode);
    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 = CodeMirror.startState(subMode);
    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 = CodeMirror.startState(htmlMode);
    511         var rubyState = CodeMirror.startState(rubyMode);
    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 });