tiddlywiki.js 10.0 KB

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