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