tiddlywiki.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. /***
  4. |''Name''|tiddlywiki.js|
  5. |''Description''|Enables TiddlyWikiy syntax highlighting using CodeMirror|
  6. |''Author''|PMario|
  7. |''Version''|0.1.7|
  8. |''Status''|''stable''|
  9. |''Source''|[[GitHub|https://github.com/pmario/CodeMirror2/blob/tw-syntax/mode/tiddlywiki]]|
  10. |''Documentation''|https://codemirror.tiddlyspace.com/|
  11. |''License''|[[MIT License|http://www.opensource.org/licenses/mit-license.php]]|
  12. |''CoreVersion''|2.5.0|
  13. |''Requires''|codemirror.js|
  14. |''Keywords''|syntax highlighting color code mirror codemirror|
  15. ! Info
  16. CoreVersion parameter is needed for TiddlyWiki only!
  17. ***/
  18. ;(function (mod) {
  19. if (typeof exports == 'object' && typeof module == 'object')
  20. // CommonJS
  21. mod(require('../../lib/codemirror'))
  22. else if (typeof define == 'function' && define.amd)
  23. // AMD
  24. define(['../../lib/codemirror'], mod)
  25. // Plain browser env
  26. else mod(CodeMirror)
  27. })(function (CodeMirror) {
  28. 'use strict'
  29. CodeMirror.defineMode('tiddlywiki', function () {
  30. // Tokenizer
  31. var textwords = {}
  32. var keywords = {
  33. allTags: true,
  34. closeAll: true,
  35. list: true,
  36. newJournal: true,
  37. newTiddler: true,
  38. permaview: true,
  39. saveChanges: true,
  40. search: true,
  41. slider: true,
  42. tabs: true,
  43. tag: true,
  44. tagging: true,
  45. tags: true,
  46. tiddler: true,
  47. timeline: true,
  48. today: true,
  49. version: true,
  50. option: true,
  51. with: true,
  52. filter: true,
  53. }
  54. var isSpaceName = /[\w_\-]/i,
  55. reHR = /^\-\-\-\-+$/, // <hr>
  56. reWikiCommentStart = /^\/\*\*\*$/, // /***
  57. reWikiCommentStop = /^\*\*\*\/$/, // ***/
  58. reBlockQuote = /^<<<$/,
  59. reJsCodeStart = /^\/\/\{\{\{$/, // //{{{ js block start
  60. reJsCodeStop = /^\/\/\}\}\}$/, // //}}} js stop
  61. reXmlCodeStart = /^<!--\{\{\{-->$/, // xml block start
  62. reXmlCodeStop = /^<!--\}\}\}-->$/, // xml stop
  63. reCodeBlockStart = /^\{\{\{$/, // {{{ TW text div block start
  64. reCodeBlockStop = /^\}\}\}$/, // }}} TW text stop
  65. reUntilCodeStop = /.*?\}\}\}/
  66. function chain(stream, state, f) {
  67. state.tokenize = f
  68. return f(stream, state)
  69. }
  70. function tokenBase(stream, state) {
  71. var sol = stream.sol(),
  72. ch = stream.peek()
  73. state.block = false // indicates the start of a code block.
  74. // check start of blocks
  75. if (sol && /[<\/\*{}\-]/.test(ch)) {
  76. if (stream.match(reCodeBlockStart)) {
  77. state.block = true
  78. return chain(stream, state, twTokenCode)
  79. }
  80. if (stream.match(reBlockQuote)) return 'quote'
  81. if (stream.match(reWikiCommentStart) || stream.match(reWikiCommentStop)) return 'comment'
  82. if (stream.match(reJsCodeStart) || stream.match(reJsCodeStop) || stream.match(reXmlCodeStart) || stream.match(reXmlCodeStop)) return 'comment'
  83. if (stream.match(reHR)) return 'hr'
  84. }
  85. stream.next()
  86. if (sol && /[\/\*!#;:>|]/.test(ch)) {
  87. if (ch == '!') {
  88. // tw header
  89. stream.skipToEnd()
  90. return 'header'
  91. }
  92. if (ch == '*') {
  93. // tw list
  94. stream.eatWhile('*')
  95. return 'comment'
  96. }
  97. if (ch == '#') {
  98. // tw numbered list
  99. stream.eatWhile('#')
  100. return 'comment'
  101. }
  102. if (ch == ';') {
  103. // definition list, term
  104. stream.eatWhile(';')
  105. return 'comment'
  106. }
  107. if (ch == ':') {
  108. // definition list, description
  109. stream.eatWhile(':')
  110. return 'comment'
  111. }
  112. if (ch == '>') {
  113. // single line quote
  114. stream.eatWhile('>')
  115. return 'quote'
  116. }
  117. if (ch == '|') return 'header'
  118. }
  119. if (ch == '{' && stream.match('{{')) return chain(stream, state, twTokenCode)
  120. // rudimentary html:// file:// link matching. TW knows much more ...
  121. if (/[hf]/i.test(ch) && /[ti]/i.test(stream.peek()) && stream.match(/\b(ttps?|tp|ile):\/\/[\-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/i)) return 'link'
  122. // just a little string indicator, don't want to have the whole string covered
  123. if (ch == '"') return 'string'
  124. if (ch == '~')
  125. // _no_ CamelCase indicator should be bold
  126. return 'brace'
  127. if (/[\[\]]/.test(ch) && stream.match(ch))
  128. // check for [[..]]
  129. return 'brace'
  130. if (ch == '@') {
  131. // check for space link. TODO fix @@...@@ highlighting
  132. stream.eatWhile(isSpaceName)
  133. return 'link'
  134. }
  135. if (/\d/.test(ch)) {
  136. // numbers
  137. stream.eatWhile(/\d/)
  138. return 'number'
  139. }
  140. if (ch == '/') {
  141. // tw invisible comment
  142. if (stream.eat('%')) {
  143. return chain(stream, state, twTokenComment)
  144. } else if (stream.eat('/')) {
  145. //
  146. return chain(stream, state, twTokenEm)
  147. }
  148. }
  149. if (ch == '_' && stream.eat('_'))
  150. // tw underline
  151. return chain(stream, state, twTokenUnderline)
  152. // strikethrough and mdash handling
  153. if (ch == '-' && stream.eat('-')) {
  154. // if strikethrough looks ugly, change CSS.
  155. if (stream.peek() != ' ') return chain(stream, state, twTokenStrike)
  156. // mdash
  157. if (stream.peek() == ' ') return 'brace'
  158. }
  159. if (ch == "'" && stream.eat("'"))
  160. // tw bold
  161. return chain(stream, state, twTokenStrong)
  162. if (ch == '<' && stream.eat('<'))
  163. // tw macro
  164. return chain(stream, state, twTokenMacro)
  165. // core macro handling
  166. stream.eatWhile(/[\w\$_]/)
  167. return textwords.propertyIsEnumerable(stream.current()) ? 'keyword' : null
  168. }
  169. // tw invisible comment
  170. function twTokenComment(stream, state) {
  171. var maybeEnd = false,
  172. ch
  173. while ((ch = stream.next())) {
  174. if (ch == '/' && maybeEnd) {
  175. state.tokenize = tokenBase
  176. break
  177. }
  178. maybeEnd = ch == '%'
  179. }
  180. return 'comment'
  181. }
  182. // tw strong / bold
  183. function twTokenStrong(stream, state) {
  184. var maybeEnd = false,
  185. ch
  186. while ((ch = stream.next())) {
  187. if (ch == "'" && maybeEnd) {
  188. state.tokenize = tokenBase
  189. break
  190. }
  191. maybeEnd = ch == "'"
  192. }
  193. return 'strong'
  194. }
  195. // tw code
  196. function twTokenCode(stream, state) {
  197. var sb = state.block
  198. if (sb && stream.current()) {
  199. return 'comment'
  200. }
  201. if (!sb && stream.match(reUntilCodeStop)) {
  202. state.tokenize = tokenBase
  203. return 'comment'
  204. }
  205. if (sb && stream.sol() && stream.match(reCodeBlockStop)) {
  206. state.tokenize = tokenBase
  207. return 'comment'
  208. }
  209. stream.next()
  210. return 'comment'
  211. }
  212. // tw em / italic
  213. function twTokenEm(stream, state) {
  214. var maybeEnd = false,
  215. ch
  216. while ((ch = stream.next())) {
  217. if (ch == '/' && maybeEnd) {
  218. state.tokenize = tokenBase
  219. break
  220. }
  221. maybeEnd = ch == '/'
  222. }
  223. return 'em'
  224. }
  225. // tw underlined text
  226. function twTokenUnderline(stream, state) {
  227. var maybeEnd = false,
  228. ch
  229. while ((ch = stream.next())) {
  230. if (ch == '_' && maybeEnd) {
  231. state.tokenize = tokenBase
  232. break
  233. }
  234. maybeEnd = ch == '_'
  235. }
  236. return 'underlined'
  237. }
  238. // tw strike through text looks ugly
  239. // change CSS if needed
  240. function twTokenStrike(stream, state) {
  241. var maybeEnd = false,
  242. ch
  243. while ((ch = stream.next())) {
  244. if (ch == '-' && maybeEnd) {
  245. state.tokenize = tokenBase
  246. break
  247. }
  248. maybeEnd = ch == '-'
  249. }
  250. return 'strikethrough'
  251. }
  252. // macro
  253. function twTokenMacro(stream, state) {
  254. if (stream.current() == '<<') {
  255. return 'macro'
  256. }
  257. var ch = stream.next()
  258. if (!ch) {
  259. state.tokenize = tokenBase
  260. return null
  261. }
  262. if (ch == '>') {
  263. if (stream.peek() == '>') {
  264. stream.next()
  265. state.tokenize = tokenBase
  266. return 'macro'
  267. }
  268. }
  269. stream.eatWhile(/[\w\$_]/)
  270. return keywords.propertyIsEnumerable(stream.current()) ? 'keyword' : null
  271. }
  272. // Interface
  273. return {
  274. startState: function () {
  275. return { tokenize: tokenBase }
  276. },
  277. token: function (stream, state) {
  278. if (stream.eatSpace()) return null
  279. var style = state.tokenize(stream, state)
  280. return style
  281. },
  282. }
  283. })
  284. CodeMirror.defineMIME('text/x-tiddlywiki', 'tiddlywiki')
  285. })