Fix: merge conflict
[myslice.git] / to-be-integrated / third-party / codemirror-3.15 / mode / markdown / markdown.js
1 CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
2
3   var htmlFound = CodeMirror.modes.hasOwnProperty("xml");
4   var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? {name: "xml", htmlMode: true} : "text/plain");
5   var aliases = {
6     html: "htmlmixed",
7     js: "javascript",
8     json: "application/json",
9     c: "text/x-csrc",
10     "c++": "text/x-c++src",
11     java: "text/x-java",
12     csharp: "text/x-csharp",
13     "c#": "text/x-csharp",
14     scala: "text/x-scala"
15   };
16
17   var getMode = (function () {
18     var i, modes = {}, mimes = {}, mime;
19
20     var list = [];
21     for (var m in CodeMirror.modes)
22       if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m);
23     for (i = 0; i < list.length; i++) {
24       modes[list[i]] = list[i];
25     }
26     var mimesList = [];
27     for (var m in CodeMirror.mimeModes)
28       if (CodeMirror.mimeModes.propertyIsEnumerable(m))
29         mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]});
30     for (i = 0; i < mimesList.length; i++) {
31       mime = mimesList[i].mime;
32       mimes[mime] = mimesList[i].mime;
33     }
34
35     for (var a in aliases) {
36       if (aliases[a] in modes || aliases[a] in mimes)
37         modes[a] = aliases[a];
38     }
39
40     return function (lang) {
41       return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null;
42     };
43   }());
44
45   // Should underscores in words open/close em/strong?
46   if (modeCfg.underscoresBreakWords === undefined)
47     modeCfg.underscoresBreakWords = true;
48
49   // Turn on fenced code blocks? ("```" to start/end)
50   if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false;
51
52   // Turn on task lists? ("- [ ] " and "- [x] ")
53   if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;
54
55   var codeDepth = 0;
56
57   var header   = 'header'
58   ,   code     = 'comment'
59   ,   quote1   = 'atom'
60   ,   quote2   = 'number'
61   ,   list1    = 'variable-2'
62   ,   list2    = 'variable-3'
63   ,   list3    = 'keyword'
64   ,   hr       = 'hr'
65   ,   image    = 'tag'
66   ,   linkinline = 'link'
67   ,   linkemail = 'link'
68   ,   linktext = 'link'
69   ,   linkhref = 'string'
70   ,   em       = 'em'
71   ,   strong   = 'strong';
72
73   var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/
74   ,   ulRE = /^[*\-+]\s+/
75   ,   olRE = /^[0-9]+\.\s+/
76   ,   taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE
77   ,   headerRE = /^(?:\={1,}|-{1,})$/
78   ,   textRE = /^[^!\[\]*_\\<>` "'(]+/;
79
80   function switchInline(stream, state, f) {
81     state.f = state.inline = f;
82     return f(stream, state);
83   }
84
85   function switchBlock(stream, state, f) {
86     state.f = state.block = f;
87     return f(stream, state);
88   }
89
90
91   // Blocks
92
93   function blankLine(state) {
94     // Reset linkTitle state
95     state.linkTitle = false;
96     // Reset EM state
97     state.em = false;
98     // Reset STRONG state
99     state.strong = false;
100     // Reset state.quote
101     state.quote = 0;
102     if (!htmlFound && state.f == htmlBlock) {
103       state.f = inlineNormal;
104       state.block = blockNormal;
105     }
106     // Reset state.trailingSpace
107     state.trailingSpace = 0;
108     state.trailingSpaceNewLine = false;
109     // Mark this line as blank
110     state.thisLineHasContent = false;
111     return null;
112   }
113
114   function blockNormal(stream, state) {
115
116     var prevLineIsList = (state.list !== false);
117     if (state.list !== false && state.indentationDiff >= 0) { // Continued list
118       if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
119         state.indentation -= state.indentationDiff;
120       }
121       state.list = null;
122     } else if (state.list !== false && state.indentation > 0) {
123       state.list = null;
124       state.listDepth = Math.floor(state.indentation / 4);
125     } else if (state.list !== false) { // No longer a list
126       state.list = false;
127       state.listDepth = 0;
128     }
129
130     if (state.indentationDiff >= 4) {
131       state.indentation -= 4;
132       stream.skipToEnd();
133       return code;
134     } else if (stream.eatSpace()) {
135       return null;
136     } else if (stream.peek() === '#' || (state.prevLineHasContent && stream.match(headerRE)) ) {
137       state.header = true;
138     } else if (stream.eat('>')) {
139       state.indentation++;
140       state.quote = 1;
141       stream.eatSpace();
142       while (stream.eat('>')) {
143         stream.eatSpace();
144         state.quote++;
145       }
146     } else if (stream.peek() === '[') {
147       return switchInline(stream, state, footnoteLink);
148     } else if (stream.match(hrRE, true)) {
149       return hr;
150     } else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, true) || stream.match(olRE, true))) {
151       state.indentation += 4;
152       state.list = true;
153       state.listDepth++;
154       if (modeCfg.taskLists && stream.match(taskListRE, false)) {
155         state.taskList = true;
156       }
157     } else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) {
158       // try switching mode
159       state.localMode = getMode(RegExp.$1);
160       if (state.localMode) state.localState = state.localMode.startState();
161       switchBlock(stream, state, local);
162       return code;
163     }
164
165     return switchInline(stream, state, state.inline);
166   }
167
168   function htmlBlock(stream, state) {
169     var style = htmlMode.token(stream, state.htmlState);
170     if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) {
171       state.f = inlineNormal;
172       state.block = blockNormal;
173     }
174     if (state.md_inside && stream.current().indexOf(">")!=-1) {
175       state.f = inlineNormal;
176       state.block = blockNormal;
177       state.htmlState.context = undefined;
178     }
179     return style;
180   }
181
182   function local(stream, state) {
183     if (stream.sol() && stream.match(/^```/, true)) {
184       state.localMode = state.localState = null;
185       state.f = inlineNormal;
186       state.block = blockNormal;
187       return code;
188     } else if (state.localMode) {
189       return state.localMode.token(stream, state.localState);
190     } else {
191       stream.skipToEnd();
192       return code;
193     }
194   }
195
196   // Inline
197   function getType(state) {
198     var styles = [];
199
200     if (state.taskOpen) { return "meta"; }
201     if (state.taskClosed) { return "property"; }
202
203     if (state.strong) { styles.push(strong); }
204     if (state.em) { styles.push(em); }
205
206     if (state.linkText) { styles.push(linktext); }
207
208     if (state.code) { styles.push(code); }
209
210     if (state.header) { styles.push(header); }
211     if (state.quote) { styles.push(state.quote % 2 ? quote1 : quote2); }
212     if (state.list !== false) {
213       var listMod = (state.listDepth - 1) % 3;
214       if (!listMod) {
215         styles.push(list1);
216       } else if (listMod === 1) {
217         styles.push(list2);
218       } else {
219         styles.push(list3);
220       }
221     }
222
223     if (state.trailingSpaceNewLine) {
224       styles.push("trailing-space-new-line");
225     } else if (state.trailingSpace) {
226       styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b"));
227     }
228
229     return styles.length ? styles.join(' ') : null;
230   }
231
232   function handleText(stream, state) {
233     if (stream.match(textRE, true)) {
234       return getType(state);
235     }
236     return undefined;
237   }
238
239   function inlineNormal(stream, state) {
240     var style = state.text(stream, state);
241     if (typeof style !== 'undefined')
242       return style;
243
244     if (state.list) { // List marker (*, +, -, 1., etc)
245       state.list = null;
246       return getType(state);
247     }
248
249     if (state.taskList) {
250       var taskOpen = stream.match(taskListRE, true)[1] !== "x";
251       if (taskOpen) state.taskOpen = true;
252       else state.taskClosed = true;
253       state.taskList = false;
254       return getType(state);
255     }
256
257     state.taskOpen = false;
258     state.taskClosed = false;
259
260     var ch = stream.next();
261
262     if (ch === '\\') {
263       stream.next();
264       return getType(state);
265     }
266
267     // Matches link titles present on next line
268     if (state.linkTitle) {
269       state.linkTitle = false;
270       var matchCh = ch;
271       if (ch === '(') {
272         matchCh = ')';
273       }
274       matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
275       var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
276       if (stream.match(new RegExp(regex), true)) {
277         return linkhref;
278       }
279     }
280
281     // If this block is changed, it may need to be updated in GFM mode
282     if (ch === '`') {
283       var t = getType(state);
284       var before = stream.pos;
285       stream.eatWhile('`');
286       var difference = 1 + stream.pos - before;
287       if (!state.code) {
288         codeDepth = difference;
289         state.code = true;
290         return getType(state);
291       } else {
292         if (difference === codeDepth) { // Must be exact
293           state.code = false;
294           return t;
295         }
296         return getType(state);
297       }
298     } else if (state.code) {
299       return getType(state);
300     }
301
302     if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) {
303       stream.match(/\[[^\]]*\]/);
304       state.inline = state.f = linkHref;
305       return image;
306     }
307
308     if (ch === '[' && stream.match(/.*\](\(| ?\[)/, false)) {
309       state.linkText = true;
310       return getType(state);
311     }
312
313     if (ch === ']' && state.linkText) {
314       var type = getType(state);
315       state.linkText = false;
316       state.inline = state.f = linkHref;
317       return type;
318     }
319
320     if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) {
321       return switchInline(stream, state, inlineElement(linkinline, '>'));
322     }
323
324     if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) {
325       return switchInline(stream, state, inlineElement(linkemail, '>'));
326     }
327
328     if (ch === '<' && stream.match(/^\w/, false)) {
329       if (stream.string.indexOf(">")!=-1) {
330         var atts = stream.string.substring(1,stream.string.indexOf(">"));
331         if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) {
332           state.md_inside = true;
333         }
334       }
335       stream.backUp(1);
336       return switchBlock(stream, state, htmlBlock);
337     }
338
339     if (ch === '<' && stream.match(/^\/\w*?>/)) {
340       state.md_inside = false;
341       return "tag";
342     }
343
344     var ignoreUnderscore = false;
345     if (!modeCfg.underscoresBreakWords) {
346       if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) {
347         var prevPos = stream.pos - 2;
348         if (prevPos >= 0) {
349           var prevCh = stream.string.charAt(prevPos);
350           if (prevCh !== '_' && prevCh.match(/(\w)/, false)) {
351             ignoreUnderscore = true;
352           }
353         }
354       }
355     }
356     var t = getType(state);
357     if (ch === '*' || (ch === '_' && !ignoreUnderscore)) {
358       if (state.strong === ch && stream.eat(ch)) { // Remove STRONG
359         state.strong = false;
360         return t;
361       } else if (!state.strong && stream.eat(ch)) { // Add STRONG
362         state.strong = ch;
363         return getType(state);
364       } else if (state.em === ch) { // Remove EM
365         state.em = false;
366         return t;
367       } else if (!state.em) { // Add EM
368         state.em = ch;
369         return getType(state);
370       }
371     } else if (ch === ' ') {
372       if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces
373         if (stream.peek() === ' ') { // Surrounded by spaces, ignore
374           return getType(state);
375         } else { // Not surrounded by spaces, back up pointer
376           stream.backUp(1);
377         }
378       }
379     }
380
381     if (ch === ' ') {
382       if (stream.match(/ +$/, false)) {
383         state.trailingSpace++;
384       } else if (state.trailingSpace) {
385         state.trailingSpaceNewLine = true;
386       }
387     }
388
389     return getType(state);
390   }
391
392   function linkHref(stream, state) {
393     // Check if space, and return NULL if so (to avoid marking the space)
394     if(stream.eatSpace()){
395       return null;
396     }
397     var ch = stream.next();
398     if (ch === '(' || ch === '[') {
399       return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']'));
400     }
401     return 'error';
402   }
403
404   function footnoteLink(stream, state) {
405     if (stream.match(/^[^\]]*\]:/, true)) {
406       state.f = footnoteUrl;
407       return linktext;
408     }
409     return switchInline(stream, state, inlineNormal);
410   }
411
412   function footnoteUrl(stream, state) {
413     // Check if space, and return NULL if so (to avoid marking the space)
414     if(stream.eatSpace()){
415       return null;
416     }
417     // Match URL
418     stream.match(/^[^\s]+/, true);
419     // Check for link title
420     if (stream.peek() === undefined) { // End of line, set flag to check next line
421       state.linkTitle = true;
422     } else { // More content on line, check if link title
423       stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true);
424     }
425     state.f = state.inline = inlineNormal;
426     return linkhref;
427   }
428
429   var savedInlineRE = [];
430   function inlineRE(endChar) {
431     if (!savedInlineRE[endChar]) {
432       // Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741)
433       endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
434       // Match any non-endChar, escaped character, as well as the closing
435       // endChar.
436       savedInlineRE[endChar] = new RegExp('^(?:[^\\\\]|\\\\.)*?(' + endChar + ')');
437     }
438     return savedInlineRE[endChar];
439   }
440
441   function inlineElement(type, endChar, next) {
442     next = next || inlineNormal;
443     return function(stream, state) {
444       stream.match(inlineRE(endChar));
445       state.inline = state.f = next;
446       return type;
447     };
448   }
449
450   return {
451     startState: function() {
452       return {
453         f: blockNormal,
454
455         prevLineHasContent: false,
456         thisLineHasContent: false,
457
458         block: blockNormal,
459         htmlState: CodeMirror.startState(htmlMode),
460         indentation: 0,
461
462         inline: inlineNormal,
463         text: handleText,
464
465         linkText: false,
466         linkTitle: false,
467         em: false,
468         strong: false,
469         header: false,
470         taskList: false,
471         list: false,
472         listDepth: 0,
473         quote: 0,
474         trailingSpace: 0,
475         trailingSpaceNewLine: false
476       };
477     },
478
479     copyState: function(s) {
480       return {
481         f: s.f,
482
483         prevLineHasContent: s.prevLineHasContent,
484         thisLineHasContent: s.thisLineHasContent,
485
486         block: s.block,
487         htmlState: CodeMirror.copyState(htmlMode, s.htmlState),
488         indentation: s.indentation,
489
490         localMode: s.localMode,
491         localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null,
492
493         inline: s.inline,
494         text: s.text,
495         linkTitle: s.linkTitle,
496         em: s.em,
497         strong: s.strong,
498         header: s.header,
499         taskList: s.taskList,
500         list: s.list,
501         listDepth: s.listDepth,
502         quote: s.quote,
503         trailingSpace: s.trailingSpace,
504         trailingSpaceNewLine: s.trailingSpaceNewLine,
505         md_inside: s.md_inside
506       };
507     },
508
509     token: function(stream, state) {
510       if (stream.sol()) {
511         if (stream.match(/^\s*$/, true)) {
512           state.prevLineHasContent = false;
513           return blankLine(state);
514         } else {
515           state.prevLineHasContent = state.thisLineHasContent;
516           state.thisLineHasContent = true;
517         }
518
519         // Reset state.header
520         state.header = false;
521
522         // Reset state.taskList
523         state.taskList = false;
524
525         // Reset state.code
526         state.code = false;
527
528         // Reset state.trailingSpace
529         state.trailingSpace = 0;
530         state.trailingSpaceNewLine = false;
531
532         state.f = state.block;
533         var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, '    ').length;
534         var difference = Math.floor((indentation - state.indentation) / 4) * 4;
535         if (difference > 4) difference = 4;
536         var adjustedIndentation = state.indentation + difference;
537         state.indentationDiff = adjustedIndentation - state.indentation;
538         state.indentation = adjustedIndentation;
539         if (indentation > 0) return null;
540       }
541       return state.f(stream, state);
542     },
543
544     blankLine: blankLine,
545
546     getType: getType
547   };
548
549 }, "xml");
550
551 CodeMirror.defineMIME("text/x-markdown", "markdown");