jsbasic/cm2/mode/markdown/markdown.js
2012-06-05 12:02:39 -04:00

231 lines
5.3 KiB
JavaScript

CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
var htmlMode = CodeMirror.getMode(cmCfg, { name: 'xml', htmlMode: true });
var header = 'header'
, code = 'code'
, quote = 'quote'
, list = 'list'
, hr = 'hr'
, linktext = 'linktext'
, linkhref = 'linkhref'
, em = 'em'
, strong = 'strong'
, emstrong = 'emstrong';
var hrRE = /^[*-=_]/
, ulRE = /^[*-+]\s+/
, olRE = /^[0-9]\.\s+/
, headerRE = /^(?:\={3,}|-{3,})$/
, codeRE = /^(k:\t|\s{4,})/
, 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 blockNormal(stream, state) {
if (stream.match(codeRE)) {
stream.skipToEnd();
return code;
}
if (stream.eatSpace()) {
return null;
}
if (stream.peek() === '#' || stream.match(headerRE)) {
stream.skipToEnd();
return header;
}
if (stream.eat('>')) {
state.indentation++;
return quote;
}
if (stream.peek() === '<') {
return switchBlock(stream, state, htmlBlock);
}
if (stream.peek() === '[') {
return switchInline(stream, state, footnoteLink);
}
if (hrRE.test(stream.peek())) {
var re = new RegExp('(?:\s*['+stream.peek()+']){3,}$');
if (stream.match(re, true)) {
return hr;
}
}
var match;
if (match = stream.match(ulRE, true) || stream.match(olRE, true)) {
state.indentation += match[0].length;
return list;
}
return switchInline(stream, state, state.inline);
}
function htmlBlock(stream, state) {
var type = htmlMode.token(stream, state.htmlState);
if (stream.eol() && !state.htmlState.context) {
state.block = blockNormal;
}
return type;
}
// Inline
function inlineNormal(stream, state) {
function getType() {
return state.strong ? (state.em ? emstrong : strong)
: (state.em ? em : null);
}
if (stream.match(textRE, true)) {
return getType();
}
var ch = stream.next();
if (ch === '\\') {
stream.next();
return getType();
}
if (ch === '`') {
return switchInline(stream, state, inlineElement(code, '`'));
}
if (ch === '<') {
return switchInline(stream, state, inlineElement(linktext, '>'));
}
if (ch === '[') {
return switchInline(stream, state, linkText);
}
var t = getType();
if (ch === '*' || ch === '_') {
if (stream.eat(ch)) {
return (state.strong = !state.strong) ? getType() : t;
}
return (state.em = !state.em) ? getType() : t;
}
return getType();
}
function linkText(stream, state) {
while (!stream.eol()) {
var ch = stream.next();
if (ch === '\\') stream.next();
if (ch === ']') {
state.inline = state.f = linkHref;
return linktext;
}
}
return linktext;
}
function linkHref(stream, state) {
stream.eatSpace();
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) {
stream.eatSpace();
stream.match(/^[^\s]+/, true);
state.f = state.inline = inlineNormal;
return linkhref;
}
function inlineElement(type, endChar, next) {
next = next || inlineNormal;
return function(stream, state) {
while (!stream.eol()) {
var ch = stream.next();
if (ch === '\\') stream.next();
if (ch === endChar) {
state.inline = state.f = next;
return type;
}
}
return type;
};
}
return {
startState: function() {
return {
f: blockNormal,
block: blockNormal,
htmlState: htmlMode.startState(),
indentation: 0,
inline: inlineNormal,
em: false,
strong: false
};
},
copyState: function(s) {
return {
f: s.f,
block: s.block,
htmlState: CodeMirror.copyState(htmlMode, s.htmlState),
indentation: s.indentation,
inline: s.inline,
em: s.em,
strong: s.strong
};
},
token: function(stream, state) {
if (stream.sol()) {
state.f = state.block;
var previousIndentation = state.indentation
, currentIndentation = 0;
while (previousIndentation > 0) {
if (stream.eat(' ')) {
previousIndentation--;
currentIndentation++;
} else if (previousIndentation >= 4 && stream.eat('\t')) {
previousIndentation -= 4;
currentIndentation += 4;
} else {
break;
}
}
state.indentation = currentIndentation;
if (currentIndentation > 0) return null;
}
return state.f(stream, state);
}
};
});
CodeMirror.defineMIME("text/x-markdown", "markdown");