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