CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { var htmlFound = CodeMirror.mimeModes.hasOwnProperty("text/html"); var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? "text/html" : "text/plain"); var aliases = { html: "htmlmixed", js: "javascript", json: "application/json", c: "text/x-csrc", "c++": "text/x-c++src", java: "text/x-java", csharp: "text/x-csharp", "c#": "text/x-csharp", scala: "text/x-scala" }; var getMode = (function () { var i, modes = {}, mimes = {}, mime; var list = []; for (var m in CodeMirror.modes) if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m); for (i = 0; i < list.length; i++) { modes[list[i]] = list[i]; } var mimesList = []; for (var m in CodeMirror.mimeModes) if (CodeMirror.mimeModes.propertyIsEnumerable(m)) mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]}); for (i = 0; i < mimesList.length; i++) { mime = mimesList[i].mime; mimes[mime] = mimesList[i].mime; } for (var a in aliases) { if (aliases[a] in modes || aliases[a] in mimes) modes[a] = aliases[a]; } return function (lang) { return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null; }; }()); // Should underscores in words open/close em/strong? if (modeCfg.underscoresBreakWords === undefined) modeCfg.underscoresBreakWords = true; // Turn on fenced code blocks? ("```" to start/end) if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false; // Turn on task lists? ("- [ ] " and "- [x] ") if (modeCfg.taskLists === undefined) modeCfg.taskLists = false; var codeDepth = 0; var header = 'header' , code = 'comment' , quote1 = 'atom' , quote2 = 'number' , list1 = 'variable-2' , list2 = 'variable-3' , list3 = 'keyword' , hr = 'hr' , image = 'tag' , linkinline = 'link' , linkemail = 'link' , linktext = 'link' , linkhref = 'string' , em = 'em' , strong = 'strong'; var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/ , ulRE = /^[*\-+]\s+/ , olRE = /^[0-9]+\.\s+/ , taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE , headerRE = /^(?:\={1,}|-{1,})$/ , textRE = /^[^!\[\]*_\\<>` "'(]+/; function switchInline(stream, state, f) { state.f = state.inline = f; return f(stream, state); } function switchBlock(stream, state, f) { state.f = state.block = f; return f(stream, state); } // Blocks function blankLine(state) { // Reset linkTitle state state.linkTitle = false; // Reset EM state state.em = false; // Reset STRONG state state.strong = false; // Reset state.quote state.quote = 0; if (!htmlFound && state.f == htmlBlock) { state.f = inlineNormal; state.block = blockNormal; } // Mark this line as blank state.thisLineHasContent = false; return null; } function blockNormal(stream, state) { var prevLineIsList = (state.list !== false); if (state.list !== false && state.indentationDiff >= 0) { // Continued list if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block state.indentation -= state.indentationDiff; } state.list = null; } else if (state.list !== false && state.indentation > 0) { state.list = null; state.listDepth = Math.floor(state.indentation / 4); } else if (state.list !== false) { // No longer a list state.list = false; state.listDepth = 0; } if (state.indentationDiff >= 4) { state.indentation -= 4; stream.skipToEnd(); return code; } else if (stream.eatSpace()) { return null; } else if (stream.peek() === '#' || (state.prevLineHasContent && stream.match(headerRE)) ) { state.header = true; } else if (stream.eat('>')) { state.indentation++; state.quote = 1; stream.eatSpace(); while (stream.eat('>')) { stream.eatSpace(); state.quote++; } } else if (stream.peek() === '[') { return switchInline(stream, state, footnoteLink); } else if (stream.match(hrRE, true)) { return hr; } else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, true) || stream.match(olRE, true))) { state.indentation += 4; state.list = true; state.listDepth++; if (modeCfg.taskLists && stream.match(taskListRE, false)) { state.taskList = true; } } else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) { // try switching mode state.localMode = getMode(RegExp.$1); if (state.localMode) state.localState = state.localMode.startState(); switchBlock(stream, state, local); return code; } return switchInline(stream, state, state.inline); } function htmlBlock(stream, state) { var style = htmlMode.token(stream, state.htmlState); if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) { state.f = inlineNormal; state.block = blockNormal; } if (state.md_inside && stream.current().indexOf(">")!=-1) { state.f = inlineNormal; state.block = blockNormal; state.htmlState.context = undefined; } return style; } function local(stream, state) { if (stream.sol() && stream.match(/^```/, true)) { state.localMode = state.localState = null; state.f = inlineNormal; state.block = blockNormal; return code; } else if (state.localMode) { return state.localMode.token(stream, state.localState); } else { stream.skipToEnd(); return code; } } // Inline function getType(state) { var styles = []; if (state.taskOpen) { return "meta"; } if (state.taskClosed) { return "property"; } if (state.strong) { styles.push(strong); } if (state.em) { styles.push(em); } if (state.linkText) { styles.push(linktext); } if (state.code) { styles.push(code); } if (state.header) { styles.push(header); } if (state.quote) { styles.push(state.quote % 2 ? quote1 : quote2); } if (state.list !== false) { var listMod = (state.listDepth - 1) % 3; if (!listMod) { styles.push(list1); } else if (listMod === 1) { styles.push(list2); } else { styles.push(list3); } } return styles.length ? styles.join(' ') : null; } function handleText(stream, state) { if (stream.match(textRE, true)) { return getType(state); } return undefined; } function inlineNormal(stream, state) { var style = state.text(stream, state); if (typeof style !== 'undefined') return style; if (state.list) { // List marker (*, +, -, 1., etc) state.list = null; return getType(state); } if (state.taskList) { var taskOpen = stream.match(taskListRE, true)[1] !== "x"; if (taskOpen) state.taskOpen = true; else state.taskClosed = true; state.taskList = false; return getType(state); } state.taskOpen = false; state.taskClosed = false; var ch = stream.next(); if (ch === '\\') { stream.next(); return getType(state); } // Matches link titles present on next line if (state.linkTitle) { state.linkTitle = false; var matchCh = ch; if (ch === '(') { matchCh = ')'; } matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; if (stream.match(new RegExp(regex), true)) { return linkhref; } } // If this block is changed, it may need to be updated in GFM mode if (ch === '`') { var t = getType(state); var before = stream.pos; stream.eatWhile('`'); var difference = 1 + stream.pos - before; if (!state.code) { codeDepth = difference; state.code = true; return getType(state); } else { if (difference === codeDepth) { // Must be exact state.code = false; return t; } return getType(state); } } else if (state.code) { return getType(state); } if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) { stream.match(/\[[^\]]*\]/); state.inline = state.f = linkHref; return image; } if (ch === '[' && stream.match(/.*\](\(| ?\[)/, false)) { state.linkText = true; return getType(state); } if (ch === ']' && state.linkText) { var type = getType(state); state.linkText = false; state.inline = state.f = linkHref; return type; } if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, true)) { return switchInline(stream, state, inlineElement(linkinline, '>')); } if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, true)) { return switchInline(stream, state, inlineElement(linkemail, '>')); } if (ch === '<' && stream.match(/^\w/, false)) { if (stream.string.indexOf(">")!=-1) { var atts = stream.string.substring(1,stream.string.indexOf(">")); if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) { state.md_inside = true; } } stream.backUp(1); return switchBlock(stream, state, htmlBlock); } if (ch === '<' && stream.match(/^\/\w*?>/)) { state.md_inside = false; return "tag"; } var ignoreUnderscore = false; if (!modeCfg.underscoresBreakWords) { if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) { var prevPos = stream.pos - 2; if (prevPos >= 0) { var prevCh = stream.string.charAt(prevPos); if (prevCh !== '_' && prevCh.match(/(\w)/, false)) { ignoreUnderscore = true; } } } } var t = getType(state); if (ch === '*' || (ch === '_' && !ignoreUnderscore)) { if (state.strong === ch && stream.eat(ch)) { // Remove STRONG state.strong = false; return t; } else if (!state.strong && stream.eat(ch)) { // Add STRONG state.strong = ch; return getType(state); } else if (state.em === ch) { // Remove EM state.em = false; return t; } else if (!state.em) { // Add EM state.em = ch; return getType(state); } } else if (ch === ' ') { if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces if (stream.peek() === ' ') { // Surrounded by spaces, ignore return getType(state); } else { // Not surrounded by spaces, back up pointer stream.backUp(1); } } } return getType(state); } function linkHref(stream, state) { // Check if space, and return NULL if so (to avoid marking the space) if(stream.eatSpace()){ return null; } var ch = stream.next(); if (ch === '(' || ch === '[') { return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']')); } return 'error'; } function footnoteLink(stream, state) { if (stream.match(/^[^\]]*\]:/, true)) { state.f = footnoteUrl; return linktext; } return switchInline(stream, state, inlineNormal); } function footnoteUrl(stream, state) { // Check if space, and return NULL if so (to avoid marking the space) if(stream.eatSpace()){ return null; } // Match URL stream.match(/^[^\s]+/, true); // Check for link title if (stream.peek() === undefined) { // End of line, set flag to check next line state.linkTitle = true; } else { // More content on line, check if link title stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); } state.f = state.inline = inlineNormal; return linkhref; } var savedInlineRE = []; function inlineRE(endChar) { if (!savedInlineRE[endChar]) { // Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741) endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); // Match any non-endChar, escaped character, as well as the closing // endChar. savedInlineRE[endChar] = new RegExp('^(?:[^\\\\]|\\\\.)*?(' + endChar + ')'); } return savedInlineRE[endChar]; } function inlineElement(type, endChar, next) { next = next || inlineNormal; return function(stream, state) { stream.match(inlineRE(endChar)); state.inline = state.f = next; return type; }; } return { startState: function() { return { f: blockNormal, prevLineHasContent: false, thisLineHasContent: false, block: blockNormal, htmlState: CodeMirror.startState(htmlMode), indentation: 0, inline: inlineNormal, text: handleText, linkText: false, linkTitle: false, em: false, strong: false, header: false, taskList: false, list: false, listDepth: 0, quote: 0 }; }, copyState: function(s) { return { f: s.f, prevLineHasContent: s.prevLineHasContent, thisLineHasContent: s.thisLineHasContent, block: s.block, htmlState: CodeMirror.copyState(htmlMode, s.htmlState), indentation: s.indentation, localMode: s.localMode, localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null, inline: s.inline, text: s.text, linkTitle: s.linkTitle, em: s.em, strong: s.strong, header: s.header, taskList: s.taskList, list: s.list, listDepth: s.listDepth, quote: s.quote, md_inside: s.md_inside }; }, token: function(stream, state) { if (stream.sol()) { if (stream.match(/^\s*$/, true)) { state.prevLineHasContent = false; return blankLine(state); } else { state.prevLineHasContent = state.thisLineHasContent; state.thisLineHasContent = true; } // Reset state.header state.header = false; // Reset state.taskList state.taskList = false; // Reset state.code state.code = false; state.f = state.block; var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length; var difference = Math.floor((indentation - state.indentation) / 4) * 4; if (difference > 4) difference = 4; var adjustedIndentation = state.indentation + difference; state.indentationDiff = adjustedIndentation - state.indentation; state.indentation = adjustedIndentation; if (indentation > 0) return null; } return state.f(stream, state); }, blankLine: blankLine, getType: getType }; }, "xml"); CodeMirror.defineMIME("text/x-markdown", "markdown");