You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

308 lines
8.3 KiB

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