openrat-cms

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

textile.js (13835B)


      1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
      2 // Distributed under an MIT license: http://codemirror.net/LICENSE
      3 
      4 (function(mod) {
      5   if (typeof exports == "object" && typeof module == "object") { // CommonJS
      6     mod(require("../../lib/codemirror"));
      7   } else if (typeof define == "function" && define.amd) { // AMD
      8     define(["../../lib/codemirror"], mod);
      9   } else { // Plain browser env
     10     mod(CodeMirror);
     11   }
     12 })(function(CodeMirror) {
     13   "use strict";
     14 
     15   var TOKEN_STYLES = {
     16     addition: "positive",
     17     attributes: "attribute",
     18     bold: "strong",
     19     cite: "keyword",
     20     code: "atom",
     21     definitionList: "number",
     22     deletion: "negative",
     23     div: "punctuation",
     24     em: "em",
     25     footnote: "variable",
     26     footCite: "qualifier",
     27     header: "header",
     28     html: "comment",
     29     image: "string",
     30     italic: "em",
     31     link: "link",
     32     linkDefinition: "link",
     33     list1: "variable-2",
     34     list2: "variable-3",
     35     list3: "keyword",
     36     notextile: "string-2",
     37     pre: "operator",
     38     p: "property",
     39     quote: "bracket",
     40     span: "quote",
     41     specialChar: "tag",
     42     strong: "strong",
     43     sub: "builtin",
     44     sup: "builtin",
     45     table: "variable-3",
     46     tableHeading: "operator"
     47   };
     48 
     49   function startNewLine(stream, state) {
     50     state.mode = Modes.newLayout;
     51     state.tableHeading = false;
     52 
     53     if (state.layoutType === "definitionList" && state.spanningLayout &&
     54         stream.match(RE("definitionListEnd"), false))
     55       state.spanningLayout = false;
     56   }
     57 
     58   function handlePhraseModifier(stream, state, ch) {
     59     if (ch === "_") {
     60       if (stream.eat("_"))
     61         return togglePhraseModifier(stream, state, "italic", /__/, 2);
     62       else
     63         return togglePhraseModifier(stream, state, "em", /_/, 1);
     64     }
     65 
     66     if (ch === "*") {
     67       if (stream.eat("*")) {
     68         return togglePhraseModifier(stream, state, "bold", /\*\*/, 2);
     69       }
     70       return togglePhraseModifier(stream, state, "strong", /\*/, 1);
     71     }
     72 
     73     if (ch === "[") {
     74       if (stream.match(/\d+\]/)) state.footCite = true;
     75       return tokenStyles(state);
     76     }
     77 
     78     if (ch === "(") {
     79       var spec = stream.match(/^(r|tm|c)\)/);
     80       if (spec)
     81         return tokenStylesWith(state, TOKEN_STYLES.specialChar);
     82     }
     83 
     84     if (ch === "<" && stream.match(/(\w+)[^>]+>[^<]+<\/\1>/))
     85       return tokenStylesWith(state, TOKEN_STYLES.html);
     86 
     87     if (ch === "?" && stream.eat("?"))
     88       return togglePhraseModifier(stream, state, "cite", /\?\?/, 2);
     89 
     90     if (ch === "=" && stream.eat("="))
     91       return togglePhraseModifier(stream, state, "notextile", /==/, 2);
     92 
     93     if (ch === "-" && !stream.eat("-"))
     94       return togglePhraseModifier(stream, state, "deletion", /-/, 1);
     95 
     96     if (ch === "+")
     97       return togglePhraseModifier(stream, state, "addition", /\+/, 1);
     98 
     99     if (ch === "~")
    100       return togglePhraseModifier(stream, state, "sub", /~/, 1);
    101 
    102     if (ch === "^")
    103       return togglePhraseModifier(stream, state, "sup", /\^/, 1);
    104 
    105     if (ch === "%")
    106       return togglePhraseModifier(stream, state, "span", /%/, 1);
    107 
    108     if (ch === "@")
    109       return togglePhraseModifier(stream, state, "code", /@/, 1);
    110 
    111     if (ch === "!") {
    112       var type = togglePhraseModifier(stream, state, "image", /(?:\([^\)]+\))?!/, 1);
    113       stream.match(/^:\S+/); // optional Url portion
    114       return type;
    115     }
    116     return tokenStyles(state);
    117   }
    118 
    119   function togglePhraseModifier(stream, state, phraseModifier, closeRE, openSize) {
    120     var charBefore = stream.pos > openSize ? stream.string.charAt(stream.pos - openSize - 1) : null;
    121     var charAfter = stream.peek();
    122     if (state[phraseModifier]) {
    123       if ((!charAfter || /\W/.test(charAfter)) && charBefore && /\S/.test(charBefore)) {
    124         var type = tokenStyles(state);
    125         state[phraseModifier] = false;
    126         return type;
    127       }
    128     } else if ((!charBefore || /\W/.test(charBefore)) && charAfter && /\S/.test(charAfter) &&
    129                stream.match(new RegExp("^.*\\S" + closeRE.source + "(?:\\W|$)"), false)) {
    130       state[phraseModifier] = true;
    131       state.mode = Modes.attributes;
    132     }
    133     return tokenStyles(state);
    134   };
    135 
    136   function tokenStyles(state) {
    137     var disabled = textileDisabled(state);
    138     if (disabled) return disabled;
    139 
    140     var styles = [];
    141     if (state.layoutType) styles.push(TOKEN_STYLES[state.layoutType]);
    142 
    143     styles = styles.concat(activeStyles(
    144       state, "addition", "bold", "cite", "code", "deletion", "em", "footCite",
    145       "image", "italic", "link", "span", "strong", "sub", "sup", "table", "tableHeading"));
    146 
    147     if (state.layoutType === "header")
    148       styles.push(TOKEN_STYLES.header + "-" + state.header);
    149 
    150     return styles.length ? styles.join(" ") : null;
    151   }
    152 
    153   function textileDisabled(state) {
    154     var type = state.layoutType;
    155 
    156     switch(type) {
    157     case "notextile":
    158     case "code":
    159     case "pre":
    160       return TOKEN_STYLES[type];
    161     default:
    162       if (state.notextile)
    163         return TOKEN_STYLES.notextile + (type ? (" " + TOKEN_STYLES[type]) : "");
    164       return null;
    165     }
    166   }
    167 
    168   function tokenStylesWith(state, extraStyles) {
    169     var disabled = textileDisabled(state);
    170     if (disabled) return disabled;
    171 
    172     var type = tokenStyles(state);
    173     if (extraStyles)
    174       return type ? (type + " " + extraStyles) : extraStyles;
    175     else
    176       return type;
    177   }
    178 
    179   function activeStyles(state) {
    180     var styles = [];
    181     for (var i = 1; i < arguments.length; ++i) {
    182       if (state[arguments[i]])
    183         styles.push(TOKEN_STYLES[arguments[i]]);
    184     }
    185     return styles;
    186   }
    187 
    188   function blankLine(state) {
    189     var spanningLayout = state.spanningLayout, type = state.layoutType;
    190 
    191     for (var key in state) if (state.hasOwnProperty(key))
    192       delete state[key];
    193 
    194     state.mode = Modes.newLayout;
    195     if (spanningLayout) {
    196       state.layoutType = type;
    197       state.spanningLayout = true;
    198     }
    199   }
    200 
    201   var REs = {
    202     cache: {},
    203     single: {
    204       bc: "bc",
    205       bq: "bq",
    206       definitionList: /- .*?:=+/,
    207       definitionListEnd: /.*=:\s*$/,
    208       div: "div",
    209       drawTable: /\|.*\|/,
    210       foot: /fn\d+/,
    211       header: /h[1-6]/,
    212       html: /\s*<(?:\/)?(\w+)(?:[^>]+)?>(?:[^<]+<\/\1>)?/,
    213       link: /[^"]+":\S/,
    214       linkDefinition: /\[[^\s\]]+\]\S+/,
    215       list: /(?:#+|\*+)/,
    216       notextile: "notextile",
    217       para: "p",
    218       pre: "pre",
    219       table: "table",
    220       tableCellAttributes: /[\/\\]\d+/,
    221       tableHeading: /\|_\./,
    222       tableText: /[^"_\*\[\(\?\+~\^%@|-]+/,
    223       text: /[^!"_=\*\[\(<\?\+~\^%@-]+/
    224     },
    225     attributes: {
    226       align: /(?:<>|<|>|=)/,
    227       selector: /\([^\(][^\)]+\)/,
    228       lang: /\[[^\[\]]+\]/,
    229       pad: /(?:\(+|\)+){1,2}/,
    230       css: /\{[^\}]+\}/
    231     },
    232     createRe: function(name) {
    233       switch (name) {
    234       case "drawTable":
    235         return REs.makeRe("^", REs.single.drawTable, "$");
    236       case "html":
    237         return REs.makeRe("^", REs.single.html, "(?:", REs.single.html, ")*", "$");
    238       case "linkDefinition":
    239         return REs.makeRe("^", REs.single.linkDefinition, "$");
    240       case "listLayout":
    241         return REs.makeRe("^", REs.single.list, RE("allAttributes"), "*\\s+");
    242       case "tableCellAttributes":
    243         return REs.makeRe("^", REs.choiceRe(REs.single.tableCellAttributes,
    244                                             RE("allAttributes")), "+\\.");
    245       case "type":
    246         return REs.makeRe("^", RE("allTypes"));
    247       case "typeLayout":
    248         return REs.makeRe("^", RE("allTypes"), RE("allAttributes"),
    249                           "*\\.\\.?", "(\\s+|$)");
    250       case "attributes":
    251         return REs.makeRe("^", RE("allAttributes"), "+");
    252 
    253       case "allTypes":
    254         return REs.choiceRe(REs.single.div, REs.single.foot,
    255                             REs.single.header, REs.single.bc, REs.single.bq,
    256                             REs.single.notextile, REs.single.pre, REs.single.table,
    257                             REs.single.para);
    258 
    259       case "allAttributes":
    260         return REs.choiceRe(REs.attributes.selector, REs.attributes.css,
    261                             REs.attributes.lang, REs.attributes.align, REs.attributes.pad);
    262 
    263       default:
    264         return REs.makeRe("^", REs.single[name]);
    265       }
    266     },
    267     makeRe: function() {
    268       var pattern = "";
    269       for (var i = 0; i < arguments.length; ++i) {
    270         var arg = arguments[i];
    271         pattern += (typeof arg === "string") ? arg : arg.source;
    272       }
    273       return new RegExp(pattern);
    274     },
    275     choiceRe: function() {
    276       var parts = [arguments[0]];
    277       for (var i = 1; i < arguments.length; ++i) {
    278         parts[i * 2 - 1] = "|";
    279         parts[i * 2] = arguments[i];
    280       }
    281 
    282       parts.unshift("(?:");
    283       parts.push(")");
    284       return REs.makeRe.apply(null, parts);
    285     }
    286   };
    287 
    288   function RE(name) {
    289     return (REs.cache[name] || (REs.cache[name] = REs.createRe(name)));
    290   }
    291 
    292   var Modes = {
    293     newLayout: function(stream, state) {
    294       if (stream.match(RE("typeLayout"), false)) {
    295         state.spanningLayout = false;
    296         return (state.mode = Modes.blockType)(stream, state);
    297       }
    298       var newMode;
    299       if (!textileDisabled(state)) {
    300         if (stream.match(RE("listLayout"), false))
    301           newMode = Modes.list;
    302         else if (stream.match(RE("drawTable"), false))
    303           newMode = Modes.table;
    304         else if (stream.match(RE("linkDefinition"), false))
    305           newMode = Modes.linkDefinition;
    306         else if (stream.match(RE("definitionList")))
    307           newMode = Modes.definitionList;
    308         else if (stream.match(RE("html"), false))
    309           newMode = Modes.html;
    310       }
    311       return (state.mode = (newMode || Modes.text))(stream, state);
    312     },
    313 
    314     blockType: function(stream, state) {
    315       var match, type;
    316       state.layoutType = null;
    317 
    318       if (match = stream.match(RE("type")))
    319         type = match[0];
    320       else
    321         return (state.mode = Modes.text)(stream, state);
    322 
    323       if (match = type.match(RE("header"))) {
    324         state.layoutType = "header";
    325         state.header = parseInt(match[0][1]);
    326       } else if (type.match(RE("bq"))) {
    327         state.layoutType = "quote";
    328       } else if (type.match(RE("bc"))) {
    329         state.layoutType = "code";
    330       } else if (type.match(RE("foot"))) {
    331         state.layoutType = "footnote";
    332       } else if (type.match(RE("notextile"))) {
    333         state.layoutType = "notextile";
    334       } else if (type.match(RE("pre"))) {
    335         state.layoutType = "pre";
    336       } else if (type.match(RE("div"))) {
    337         state.layoutType = "div";
    338       } else if (type.match(RE("table"))) {
    339         state.layoutType = "table";
    340       }
    341 
    342       state.mode = Modes.attributes;
    343       return tokenStyles(state);
    344     },
    345 
    346     text: function(stream, state) {
    347       if (stream.match(RE("text"))) return tokenStyles(state);
    348 
    349       var ch = stream.next();
    350       if (ch === '"')
    351         return (state.mode = Modes.link)(stream, state);
    352       return handlePhraseModifier(stream, state, ch);
    353     },
    354 
    355     attributes: function(stream, state) {
    356       state.mode = Modes.layoutLength;
    357 
    358       if (stream.match(RE("attributes")))
    359         return tokenStylesWith(state, TOKEN_STYLES.attributes);
    360       else
    361         return tokenStyles(state);
    362     },
    363 
    364     layoutLength: function(stream, state) {
    365       if (stream.eat(".") && stream.eat("."))
    366         state.spanningLayout = true;
    367 
    368       state.mode = Modes.text;
    369       return tokenStyles(state);
    370     },
    371 
    372     list: function(stream, state) {
    373       var match = stream.match(RE("list"));
    374       state.listDepth = match[0].length;
    375       var listMod = (state.listDepth - 1) % 3;
    376       if (!listMod)
    377         state.layoutType = "list1";
    378       else if (listMod === 1)
    379         state.layoutType = "list2";
    380       else
    381         state.layoutType = "list3";
    382 
    383       state.mode = Modes.attributes;
    384       return tokenStyles(state);
    385     },
    386 
    387     link: function(stream, state) {
    388       state.mode = Modes.text;
    389       if (stream.match(RE("link"))) {
    390         stream.match(/\S+/);
    391         return tokenStylesWith(state, TOKEN_STYLES.link);
    392       }
    393       return tokenStyles(state);
    394     },
    395 
    396     linkDefinition: function(stream, state) {
    397       stream.skipToEnd();
    398       return tokenStylesWith(state, TOKEN_STYLES.linkDefinition);
    399     },
    400 
    401     definitionList: function(stream, state) {
    402       stream.match(RE("definitionList"));
    403 
    404       state.layoutType = "definitionList";
    405 
    406       if (stream.match(/\s*$/))
    407         state.spanningLayout = true;
    408       else
    409         state.mode = Modes.attributes;
    410 
    411       return tokenStyles(state);
    412     },
    413 
    414     html: function(stream, state) {
    415       stream.skipToEnd();
    416       return tokenStylesWith(state, TOKEN_STYLES.html);
    417     },
    418 
    419     table: function(stream, state) {
    420       state.layoutType = "table";
    421       return (state.mode = Modes.tableCell)(stream, state);
    422     },
    423 
    424     tableCell: function(stream, state) {
    425       if (stream.match(RE("tableHeading")))
    426         state.tableHeading = true;
    427       else
    428         stream.eat("|");
    429 
    430       state.mode = Modes.tableCellAttributes;
    431       return tokenStyles(state);
    432     },
    433 
    434     tableCellAttributes: function(stream, state) {
    435       state.mode = Modes.tableText;
    436 
    437       if (stream.match(RE("tableCellAttributes")))
    438         return tokenStylesWith(state, TOKEN_STYLES.attributes);
    439       else
    440         return tokenStyles(state);
    441     },
    442 
    443     tableText: function(stream, state) {
    444       if (stream.match(RE("tableText")))
    445         return tokenStyles(state);
    446 
    447       if (stream.peek() === "|") { // end of cell
    448         state.mode = Modes.tableCell;
    449         return tokenStyles(state);
    450       }
    451       return handlePhraseModifier(stream, state, stream.next());
    452     }
    453   };
    454 
    455   CodeMirror.defineMode("textile", function() {
    456     return {
    457       startState: function() {
    458         return { mode: Modes.newLayout };
    459       },
    460       token: function(stream, state) {
    461         if (stream.sol()) startNewLine(stream, state);
    462         return state.mode(stream, state);
    463       },
    464       blankLine: blankLine
    465     };
    466   });
    467 
    468   CodeMirror.defineMIME("text/x-textile", "textile");
    469 });