textile.js 15 KB

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