textile.js 14 KB

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