textile.js 14 KB

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