2 |''Name''|tiddlywiki.js|
3 |''Description''|Enables TiddlyWikiy syntax highlighting using CodeMirror|
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|
14 CoreVersion parameter is needed for TiddlyWiki only!
17 CodeMirror.defineMode("tiddlywiki", function () {
21 var keywords = function () {
23 return { type: type, style: "macro"};
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'),
35 "filter": kw('filter')
39 var isSpaceName = /[\w_\-]/i,
40 reHR = /^\-\-\-\-+$/, // <hr>
41 reWikiCommentStart = /^\/\*\*\*$/, // /***
42 reWikiCommentStop = /^\*\*\*\/$/, // ***/
43 reBlockQuote = /^<<<$/,
45 reJsCodeStart = /^\/\/\{\{\{$/, // //{{{ js block start
46 reJsCodeStop = /^\/\/\}\}\}$/, // //}}} js stop
47 reXmlCodeStart = /^<!--\{\{\{-->$/, // xml block start
48 reXmlCodeStop = /^<!--\}\}\}-->$/, // xml stop
50 reCodeBlockStart = /^\{\{\{$/, // {{{ TW text div block start
51 reCodeBlockStop = /^\}\}\}$/, // }}} TW text stop
53 reUntilCodeStop = /.*?\}\}\}/;
55 function chain(stream, state, f) {
57 return f(stream, state);
60 // Used as scratch variables to communicate multiple values without
61 // consing up tons of objects.
64 function ret(tp, style, cont) {
70 function jsTokenBase(stream, state) {
71 var sol = stream.sol(), ch;
73 state.block = false; // indicates the start of a code block.
75 ch = stream.peek(); // don't eat, to make matching simpler
77 // check start of blocks
78 if (sol && /[<\/\*{}\-]/.test(ch)) {
79 if (stream.match(reCodeBlockStart)) {
81 return chain(stream, state, twTokenCode);
83 if (stream.match(reBlockQuote)) {
84 return ret('quote', 'quote');
86 if (stream.match(reWikiCommentStart) || stream.match(reWikiCommentStop)) {
87 return ret('code', 'comment');
89 if (stream.match(reJsCodeStart) || stream.match(reJsCodeStop) || stream.match(reXmlCodeStart) || stream.match(reXmlCodeStop)) {
90 return ret('code', 'comment');
92 if (stream.match(reHR)) {
93 return ret('hr', 'hr');
98 if (sol && /[\/\*!#;:>|]/.test(ch)) {
99 if (ch == "!") { // tw header
101 return ret("header", "header");
103 if (ch == "*") { // tw list
104 stream.eatWhile('*');
105 return ret("list", "comment");
107 if (ch == "#") { // tw numbered list
108 stream.eatWhile('#');
109 return ret("list", "comment");
111 if (ch == ";") { // definition list, term
112 stream.eatWhile(';');
113 return ret("list", "comment");
115 if (ch == ":") { // definition list, description
116 stream.eatWhile(':');
117 return ret("list", "comment");
119 if (ch == ">") { // single line quote
120 stream.eatWhile(">");
121 return ret("quote", "quote");
124 return ret('table', 'header');
128 if (ch == '{' && stream.match(/\{\{/)) {
129 return chain(stream, state, twTokenCode);
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");
138 // just a little string indicator, don't want to have the whole string covered
140 return ret('string', 'string');
142 if (ch == '~') { // _no_ CamelCase indicator should be bold
143 return ret('text', 'brace');
145 if (/[\[\]]/.test(ch)) { // check for [[..]]
146 if (stream.peek() == ch) {
148 return ret('brace', 'brace');
151 if (ch == "@") { // check for space link. TODO fix @@...@@ highlighting
152 stream.eatWhile(isSpaceName);
153 return ret("link", "link");
155 if (/\d/.test(ch)) { // numbers
156 stream.eatWhile(/\d/);
157 return ret("number", "number");
159 if (ch == "/") { // tw invisible comment
160 if (stream.eat("%")) {
161 return chain(stream, state, twTokenComment);
163 else if (stream.eat("/")) { //
164 return chain(stream, state, twTokenEm);
167 if (ch == "_") { // tw underline
168 if (stream.eat("_")) {
169 return chain(stream, state, twTokenUnderline);
172 // strikethrough and mdash handling
174 if (stream.eat("-")) {
175 // if strikethrough looks ugly, change CSS.
176 if (stream.peek() != ' ')
177 return chain(stream, state, twTokenStrike);
179 if (stream.peek() == ' ')
180 return ret('text', 'brace');
183 if (ch == "'") { // tw bold
184 if (stream.eat("'")) {
185 return chain(stream, state, twTokenStrong);
188 if (ch == "<") { // tw macro
189 if (stream.eat("<")) {
190 return chain(stream, state, twTokenMacro);
197 // core macro handling
198 stream.eatWhile(/[\w\$_]/);
199 var word = stream.current(),
200 known = textwords.propertyIsEnumerable(word) && textwords[word];
202 return known ? ret(known.type, known.style, word) : ret("text", null, word);
206 // tw invisible comment
207 function twTokenComment(stream, state) {
208 var maybeEnd = false,
210 while (ch = stream.next()) {
211 if (ch == "/" && maybeEnd) {
212 state.tokenize = jsTokenBase;
215 maybeEnd = (ch == "%");
217 return ret("comment", "comment");
221 function twTokenStrong(stream, state) {
222 var maybeEnd = false,
224 while (ch = stream.next()) {
225 if (ch == "'" && maybeEnd) {
226 state.tokenize = jsTokenBase;
229 maybeEnd = (ch == "'");
231 return ret("text", "strong");
235 function twTokenCode(stream, state) {
236 var ch, sb = state.block;
238 if (sb && stream.current()) {
239 return ret("code", "comment");
242 if (!sb && stream.match(reUntilCodeStop)) {
243 state.tokenize = jsTokenBase;
244 return ret("code", "comment");
247 if (sb && stream.sol() && stream.match(reCodeBlockStop)) {
248 state.tokenize = jsTokenBase;
249 return ret("code", "comment");
253 return (sb) ? ret("code", "comment") : ret("code", "comment");
257 function twTokenEm(stream, state) {
258 var maybeEnd = false,
260 while (ch = stream.next()) {
261 if (ch == "/" && maybeEnd) {
262 state.tokenize = jsTokenBase;
265 maybeEnd = (ch == "/");
267 return ret("text", "em");
270 // tw underlined text
271 function twTokenUnderline(stream, state) {
272 var maybeEnd = false,
274 while (ch = stream.next()) {
275 if (ch == "_" && maybeEnd) {
276 state.tokenize = jsTokenBase;
279 maybeEnd = (ch == "_");
281 return ret("text", "underlined");
284 // tw strike through text looks ugly
285 // change CSS if needed
286 function twTokenStrike(stream, state) {
287 var maybeEnd = false, ch;
289 while (ch = stream.next()) {
290 if (ch == "-" && maybeEnd) {
291 state.tokenize = jsTokenBase;
294 maybeEnd = (ch == "-");
296 return ret("text", "strikethrough");
300 function twTokenMacro(stream, state) {
303 if (stream.current() == '<<') {
304 return ret('brace', 'macro');
309 state.tokenize = jsTokenBase;
313 if (stream.peek() == '>') {
315 state.tokenize = jsTokenBase;
316 return ret("brace", "macro");
320 stream.eatWhile(/[\w\$_]/);
321 word = stream.current();
322 known = keywords.propertyIsEnumerable(word) && keywords[word];
325 return ret(known.type, known.style, word);
328 return ret("macro", null, word);
334 startState: function () {
336 tokenize: jsTokenBase,
342 token: function (stream, state) {
343 if (stream.eatSpace()) return null;
344 var style = state.tokenize(stream, state);
352 CodeMirror.defineMIME("text/x-tiddlywiki", "tiddlywiki");