|
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../xml/xml"), require("../meta")); else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../xml/xml", "../meta"], mod); else // Plain browser env
mod(CodeMirror); })(function(CodeMirror) { "use strict";
CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
var htmlFound = CodeMirror.modes.hasOwnProperty("xml"); var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? {name: "xml", htmlMode: true} : "text/plain");
function getMode(name) { if (CodeMirror.findModeByName) { var found = CodeMirror.findModeByName(name); if (found) name = found.mime || found.mimes[0]; } var mode = CodeMirror.getMode(cmCfg, name); return mode.name == "null" ? null : mode; }
// Should characters that affect highlighting be highlighted separate?
// Does not include characters that will be output (such as `1.` and `-` for lists)
if (modeCfg.highlightFormatting === undefined) modeCfg.highlightFormatting = false;
// Maximum number of nested blockquotes. Set to 0 for infinite nesting.
// Excess `>` will emit `error` token.
if (modeCfg.maxBlockquoteDepth === undefined) modeCfg.maxBlockquoteDepth = 0;
// 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;
// Turn on strikethrough syntax
if (modeCfg.strikethrough === undefined) modeCfg.strikethrough = false;
var codeDepth = 0;
var header = 'header' , code = 'comment' , quote = 'quote' , list1 = 'variable-2' , list2 = 'variable-3' , list3 = 'keyword' , hr = 'hr' , image = 'tag' , formatting = 'formatting' , linkinline = 'link' , linkemail = 'link' , linktext = 'link' , linkhref = 'string' , em = 'em' , strong = 'strong' , strikethrough = 'strikethrough';
var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/ , ulRE = /^[*\-+]\s+/ , olRE = /^[0-9]+\.\s+/ , taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE
, atxHeaderRE = /^#+/ , setextHeaderRE = /^(?:\={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 strikethrough state
state.strikethrough = false; // Reset state.quote
state.quote = 0; if (!htmlFound && state.f == htmlBlock) { state.f = inlineNormal; state.block = blockNormal; } // Reset state.trailingSpace
state.trailingSpace = 0; state.trailingSpaceNewLine = false; // Mark this line as blank
state.thisLineHasContent = false; return null; }
function blockNormal(stream, state) {
var sol = stream.sol();
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; }
var match = null; if (state.indentationDiff >= 4) { state.indentation -= 4; stream.skipToEnd(); return code; } else if (stream.eatSpace()) { return null; } else if (match = stream.match(atxHeaderRE)) { state.header = match[0].length <= 6 ? match[0].length : 6; if (modeCfg.highlightFormatting) state.formatting = "header"; state.f = state.inline; return getType(state); } else if (state.prevLineHasContent && (match = stream.match(setextHeaderRE))) { state.header = match[0].charAt(0) == '=' ? 1 : 2; if (modeCfg.highlightFormatting) state.formatting = "header"; state.f = state.inline; return getType(state); } else if (stream.eat('>')) { state.indentation++; state.quote = sol ? 1 : state.quote + 1; if (modeCfg.highlightFormatting) state.formatting = "quote"; stream.eatSpace(); return getType(state); } 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, false) || stream.match(olRE, false))) { var listType = null; if (stream.match(ulRE, true)) { listType = 'ul'; } else { stream.match(olRE, true); listType = 'ol'; } state.indentation += 4; state.list = true; state.listDepth++; if (modeCfg.taskLists && stream.match(taskListRE, false)) { state.taskList = true; } state.f = state.inline; if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType]; return getType(state); } else if (modeCfg.fencedCodeBlocks && stream.match(/^```[ \t]*([\w+#]*)/, true)) { // try switching mode
state.localMode = getMode(RegExp.$1); if (state.localMode) state.localState = state.localMode.startState(); state.f = state.block = local; if (modeCfg.highlightFormatting) state.formatting = "code-block"; state.code = true; return getType(state); }
return switchInline(stream, state, state.inline); }
function htmlBlock(stream, state) { var style = htmlMode.token(stream, state.htmlState); if ((htmlFound && state.htmlState.tagStart === null && !state.htmlState.context) || (state.md_inside && stream.current().indexOf(">") > -1)) { state.f = inlineNormal; state.block = blockNormal; state.htmlState = null; } return style; }
function local(stream, state) { if (stream.sol() && stream.match("```", false)) { state.localMode = state.localState = null; state.f = state.block = leavingLocal; return null; } else if (state.localMode) { return state.localMode.token(stream, state.localState); } else { stream.skipToEnd(); return code; } }
function leavingLocal(stream, state) { stream.match("```"); state.block = blockNormal; state.f = inlineNormal; if (modeCfg.highlightFormatting) state.formatting = "code-block"; state.code = true; var returnType = getType(state); state.code = false; return returnType; }
// Inline
function getType(state) { var styles = [];
if (state.formatting) { styles.push(formatting);
if (typeof state.formatting === "string") state.formatting = [state.formatting];
for (var i = 0; i < state.formatting.length; i++) { styles.push(formatting + "-" + state.formatting[i]);
if (state.formatting[i] === "header") { styles.push(formatting + "-" + state.formatting[i] + "-" + state.header); }
// Add `formatting-quote` and `formatting-quote-#` for blockquotes
// Add `error` instead if the maximum blockquote nesting depth is passed
if (state.formatting[i] === "quote") { if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) { styles.push(formatting + "-" + state.formatting[i] + "-" + state.quote); } else { styles.push("error"); } } } }
if (state.taskOpen) { styles.push("meta"); return styles.length ? styles.join(' ') : null; } if (state.taskClosed) { styles.push("property"); return styles.length ? styles.join(' ') : null; }
if (state.linkHref) { styles.push(linkhref); return styles.length ? styles.join(' ') : null; }
if (state.strong) { styles.push(strong); } if (state.em) { styles.push(em); } if (state.strikethrough) { styles.push(strikethrough); }
if (state.linkText) { styles.push(linktext); }
if (state.code) { styles.push(code); }
if (state.header) { styles.push(header); styles.push(header + "-" + state.header); }
if (state.quote) { styles.push(quote);
// Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth
if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) { styles.push(quote + "-" + state.quote); } else { styles.push(quote + "-" + modeCfg.maxBlockquoteDepth); } }
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); } }
if (state.trailingSpaceNewLine) { styles.push("trailing-space-new-line"); } else if (state.trailingSpace) { styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b")); }
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; if (modeCfg.highlightFormatting) state.formatting = "task"; state.taskList = false; return getType(state); }
state.taskOpen = false; state.taskClosed = false;
if (state.header && stream.match(/^#+$/, true)) { if (modeCfg.highlightFormatting) state.formatting = "header"; return getType(state); }
// Get sol() value now, before character is consumed
var sol = stream.sol();
var ch = stream.next();
if (ch === '\\') { stream.next(); if (modeCfg.highlightFormatting) { var type = getType(state); return type ? type + " formatting-escape" : "formatting-escape"; } }
// 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 previousFormatting = state.formatting; if (modeCfg.highlightFormatting) state.formatting = "code"; 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; } state.formatting = previousFormatting; 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; if (modeCfg.highlightFormatting) state.formatting = "link"; return getType(state); }
if (ch === ']' && state.linkText && stream.match(/\(.*\)| ?\[.*\]/, false)) { if (modeCfg.highlightFormatting) state.formatting = "link"; var type = getType(state); state.linkText = false; state.inline = state.f = linkHref; return type; }
if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) { state.f = state.inline = linkInline; if (modeCfg.highlightFormatting) state.formatting = "link"; var type = getType(state); if (type){ type += " "; } else { type = ""; } return type + linkinline; }
if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) { state.f = state.inline = linkInline; if (modeCfg.highlightFormatting) state.formatting = "link"; var type = getType(state); if (type){ type += " "; } else { type = ""; } return type + 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); state.htmlState = CodeMirror.startState(htmlMode); 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; } } } } if (ch === '*' || (ch === '_' && !ignoreUnderscore)) { if (sol && stream.peek() === ' ') { // Do nothing, surrounded by newline and space
} else if (state.strong === ch && stream.eat(ch)) { // Remove STRONG
if (modeCfg.highlightFormatting) state.formatting = "strong"; var t = getType(state); state.strong = false; return t; } else if (!state.strong && stream.eat(ch)) { // Add STRONG
state.strong = ch; if (modeCfg.highlightFormatting) state.formatting = "strong"; return getType(state); } else if (state.em === ch) { // Remove EM
if (modeCfg.highlightFormatting) state.formatting = "em"; var t = getType(state); state.em = false; return t; } else if (!state.em) { // Add EM
state.em = ch; if (modeCfg.highlightFormatting) state.formatting = "em"; 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); } } }
if (modeCfg.strikethrough) { if (ch === '~' && stream.eatWhile(ch)) { if (state.strikethrough) {// Remove strikethrough
if (modeCfg.highlightFormatting) state.formatting = "strikethrough"; var t = getType(state); state.strikethrough = false; return t; } else if (stream.match(/^[^\s]/, false)) {// Add strikethrough
state.strikethrough = true; if (modeCfg.highlightFormatting) state.formatting = "strikethrough"; return getType(state); } } else if (ch === ' ') { if (stream.match(/^~~/, true)) { // Probably surrounded by space
if (stream.peek() === ' ') { // Surrounded by spaces, ignore
return getType(state); } else { // Not surrounded by spaces, back up pointer
stream.backUp(2); } } } }
if (ch === ' ') { if (stream.match(/ +$/, false)) { state.trailingSpace++; } else if (state.trailingSpace) { state.trailingSpaceNewLine = true; } }
return getType(state); }
function linkInline(stream, state) { var ch = stream.next();
if (ch === ">") { state.f = state.inline = inlineNormal; if (modeCfg.highlightFormatting) state.formatting = "link"; var type = getType(state); if (type){ type += " "; } else { type = ""; } return type + linkinline; }
stream.match(/^[^>]+/, true);
return linkinline; }
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 === '[') { state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]"); if (modeCfg.highlightFormatting) state.formatting = "link-string"; state.linkHref = true; return getType(state); } return 'error'; }
function getLinkHrefInside(endChar) { return function(stream, state) { var ch = stream.next();
if (ch === endChar) { state.f = state.inline = inlineNormal; if (modeCfg.highlightFormatting) state.formatting = "link-string"; var returnState = getType(state); state.linkHref = false; return returnState; }
if (stream.match(inlineRE(endChar), true)) { stream.backUp(1); }
state.linkHref = true; return getType(state); }; }
function footnoteLink(stream, state) { if (stream.match(/^[^\]]*\]:/, false)) { state.f = footnoteLinkInside; stream.next(); // Consume [
if (modeCfg.highlightFormatting) state.formatting = "link"; state.linkText = true; return getType(state); } return switchInline(stream, state, inlineNormal); }
function footnoteLinkInside(stream, state) { if (stream.match(/^\]:/, true)) { state.f = state.inline = footnoteUrl; if (modeCfg.highlightFormatting) state.formatting = "link"; var returnType = getType(state); state.linkText = false; return returnType; }
stream.match(/^[^\]]+/, true);
return linktext; }
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]; }
var mode = { startState: function() { return { f: blockNormal,
prevLineHasContent: false, thisLineHasContent: false,
block: blockNormal, htmlState: null, indentation: 0,
inline: inlineNormal, text: handleText,
formatting: false, linkText: false, linkHref: false, linkTitle: false, em: false, strong: false, header: 0, taskList: false, list: false, listDepth: 0, quote: 0, trailingSpace: 0, trailingSpaceNewLine: false, strikethrough: false }; },
copyState: function(s) { return { f: s.f,
prevLineHasContent: s.prevLineHasContent, thisLineHasContent: s.thisLineHasContent,
block: s.block, htmlState: s.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, formatting: false, linkTitle: s.linkTitle, em: s.em, strong: s.strong, strikethrough: s.strikethrough, header: s.header, taskList: s.taskList, list: s.list, listDepth: s.listDepth, quote: s.quote, trailingSpace: s.trailingSpace, trailingSpaceNewLine: s.trailingSpaceNewLine, md_inside: s.md_inside }; },
token: function(stream, state) {
// Reset state.formatting
state.formatting = false;
if (stream.sol()) { var forceBlankLine = !!state.header;
// Reset state.header
state.header = 0;
if (stream.match(/^\s*$/, true) || forceBlankLine) { state.prevLineHasContent = false; blankLine(state); return forceBlankLine ? this.token(stream, state) : null; } else { state.prevLineHasContent = state.thisLineHasContent; state.thisLineHasContent = true; }
// Reset state.taskList
state.taskList = false;
// Reset state.code
state.code = false;
// Reset state.trailingSpace
state.trailingSpace = 0; state.trailingSpaceNewLine = 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); },
innerMode: function(state) { if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode}; if (state.localState) return {state: state.localState, mode: state.localMode}; return {state: state, mode: mode}; },
blankLine: blankLine,
getType: getType,
fold: "markdown" }; return mode; }, "xml");
CodeMirror.defineMIME("text/x-markdown", "markdown");
});
|