all repos — NoPaste @ 3e3c433f026c49e04d0efa0f0f39fc688ed32bb1

Resurrected - The PussTheCat.org fork of NoPaste

scripts/CodeMirror/mode/textile/textile.js (view raw)

  1// CodeMirror, copyright (c) by Marijn Haverbeke and others
  2// Distributed under an MIT license: https://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});