tiki.js
316 lines
| 7.9 KiB
| application/javascript
|
JavascriptLexer
Matthias BUSSONNIER
|
r8053 | CodeMirror.defineMode('tiki', function(config, parserConfig) { | ||
function inBlock(style, terminator, returnTokenizer) { | ||||
return function(stream, state) { | ||||
while (!stream.eol()) { | ||||
if (stream.match(terminator)) { | ||||
state.tokenize = inText; | ||||
break; | ||||
} | ||||
stream.next(); | ||||
} | ||||
if (returnTokenizer) state.tokenize = returnTokenizer; | ||||
return style; | ||||
}; | ||||
} | ||||
function inLine(style, terminator) { | ||||
return function(stream, state) { | ||||
while(!stream.eol()) { | ||||
stream.next() | ||||
} | ||||
state.tokenize = inText; | ||||
return style; | ||||
}; | ||||
} | ||||
function inText(stream, state) { | ||||
function chain(parser) { | ||||
state.tokenize = parser; | ||||
return parser(stream, state); | ||||
} | ||||
var sol = stream.sol(); | ||||
var ch = stream.next(); | ||||
//non start of line | ||||
switch (ch) { //switch is generally much faster than if, so it is used here | ||||
case "{": //plugin | ||||
type = stream.eat("/") ? "closeTag" : "openTag"; | ||||
stream.eatSpace(); | ||||
tagName = ""; | ||||
var c; | ||||
while ((c = stream.eat(/[^\s\u00a0=\"\'\/?(}]/))) tagName += c; | ||||
state.tokenize = inPlugin; | ||||
return "tag"; | ||||
break; | ||||
case "_": //bold | ||||
if (stream.eat("_")) { | ||||
return chain(inBlock("strong", "__", inText)); | ||||
} | ||||
break; | ||||
case "'": //italics | ||||
if (stream.eat("'")) { | ||||
// Italic text | ||||
return chain(inBlock("em", "''", inText)); | ||||
} | ||||
break; | ||||
case "(":// Wiki Link | ||||
if (stream.eat("(")) { | ||||
return chain(inBlock("variable-2", "))", inText)); | ||||
} | ||||
break; | ||||
case "[":// Weblink | ||||
return chain(inBlock("variable-3", "]", inText)); | ||||
break; | ||||
case "|": //table | ||||
if (stream.eat("|")) { | ||||
return chain(inBlock("comment", "||")); | ||||
} | ||||
break; | ||||
case "-": | ||||
if (stream.eat("=")) {//titleBar | ||||
return chain(inBlock("header string", "=-", inText)); | ||||
} else if (stream.eat("-")) {//deleted | ||||
return chain(inBlock("error tw-deleted", "--", inText)); | ||||
} | ||||
break; | ||||
case "=": //underline | ||||
if (stream.match("==")) { | ||||
return chain(inBlock("tw-underline", "===", inText)); | ||||
} | ||||
break; | ||||
case ":": | ||||
if (stream.eat(":")) { | ||||
return chain(inBlock("comment", "::")); | ||||
} | ||||
break; | ||||
case "^": //box | ||||
return chain(inBlock("tw-box", "^")); | ||||
break; | ||||
case "~": //np | ||||
if (stream.match("np~")) { | ||||
return chain(inBlock("meta", "~/np~")); | ||||
} | ||||
break; | ||||
} | ||||
//start of line types | ||||
if (sol) { | ||||
switch (ch) { | ||||
case "!": //header at start of line | ||||
if (stream.match('!!!!!')) { | ||||
return chain(inLine("header string")); | ||||
} else if (stream.match('!!!!')) { | ||||
return chain(inLine("header string")); | ||||
} else if (stream.match('!!!')) { | ||||
return chain(inLine("header string")); | ||||
} else if (stream.match('!!')) { | ||||
return chain(inLine("header string")); | ||||
} else { | ||||
return chain(inLine("header string")); | ||||
} | ||||
break; | ||||
case "*": //unordered list line item, or <li /> at start of line | ||||
case "#": //ordered list line item, or <li /> at start of line | ||||
case "+": //ordered list line item, or <li /> at start of line | ||||
return chain(inLine("tw-listitem bracket")); | ||||
break; | ||||
} | ||||
} | ||||
//stream.eatWhile(/[&{]/); was eating up plugins, turned off to act less like html and more like tiki | ||||
return null; | ||||
} | ||||
var indentUnit = config.indentUnit; | ||||
// Return variables for tokenizers | ||||
var pluginName, type; | ||||
function inPlugin(stream, state) { | ||||
var ch = stream.next(); | ||||
var peek = stream.peek(); | ||||
if (ch == "}") { | ||||
state.tokenize = inText; | ||||
//type = ch == ")" ? "endPlugin" : "selfclosePlugin"; inPlugin | ||||
return "tag"; | ||||
} else if (ch == "(" || ch == ")") { | ||||
return "bracket"; | ||||
} else if (ch == "=") { | ||||
type = "equals"; | ||||
if (peek == ">") { | ||||
ch = stream.next(); | ||||
peek = stream.peek(); | ||||
} | ||||
//here we detect values directly after equal character with no quotes | ||||
if (!/[\'\"]/.test(peek)) { | ||||
state.tokenize = inAttributeNoQuote(); | ||||
} | ||||
//end detect values | ||||
return "operator"; | ||||
} else if (/[\'\"]/.test(ch)) { | ||||
state.tokenize = inAttribute(ch); | ||||
return state.tokenize(stream, state); | ||||
} else { | ||||
stream.eatWhile(/[^\s\u00a0=\"\'\/?]/); | ||||
return "keyword"; | ||||
} | ||||
} | ||||
function inAttribute(quote) { | ||||
return function(stream, state) { | ||||
while (!stream.eol()) { | ||||
if (stream.next() == quote) { | ||||
state.tokenize = inPlugin; | ||||
break; | ||||
} | ||||
} | ||||
return "string"; | ||||
}; | ||||
} | ||||
function inAttributeNoQuote() { | ||||
return function(stream, state) { | ||||
while (!stream.eol()) { | ||||
var ch = stream.next(); | ||||
var peek = stream.peek(); | ||||
if (ch == " " || ch == "," || /[ )}]/.test(peek)) { | ||||
state.tokenize = inPlugin; | ||||
break; | ||||
} | ||||
} | ||||
return "string"; | ||||
}; | ||||
} | ||||
var curState, setStyle; | ||||
function pass() { | ||||
for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); | ||||
} | ||||
function cont() { | ||||
pass.apply(null, arguments); | ||||
return true; | ||||
} | ||||
function pushContext(pluginName, startOfLine) { | ||||
var noIndent = curState.context && curState.context.noIndent; | ||||
curState.context = { | ||||
prev: curState.context, | ||||
pluginName: pluginName, | ||||
indent: curState.indented, | ||||
startOfLine: startOfLine, | ||||
noIndent: noIndent | ||||
}; | ||||
} | ||||
function popContext() { | ||||
if (curState.context) curState.context = curState.context.prev; | ||||
} | ||||
function element(type) { | ||||
if (type == "openPlugin") {curState.pluginName = pluginName; return cont(attributes, endplugin(curState.startOfLine));} | ||||
else if (type == "closePlugin") { | ||||
var err = false; | ||||
if (curState.context) { | ||||
err = curState.context.pluginName != pluginName; | ||||
popContext(); | ||||
} else { | ||||
err = true; | ||||
} | ||||
if (err) setStyle = "error"; | ||||
return cont(endcloseplugin(err)); | ||||
} | ||||
else if (type == "string") { | ||||
if (!curState.context || curState.context.name != "!cdata") pushContext("!cdata"); | ||||
if (curState.tokenize == inText) popContext(); | ||||
return cont(); | ||||
} | ||||
else return cont(); | ||||
} | ||||
function endplugin(startOfLine) { | ||||
return function(type) { | ||||
if ( | ||||
type == "selfclosePlugin" || | ||||
type == "endPlugin" | ||||
) | ||||
return cont(); | ||||
if (type == "endPlugin") {pushContext(curState.pluginName, startOfLine); return cont();} | ||||
return cont(); | ||||
}; | ||||
} | ||||
function endcloseplugin(err) { | ||||
return function(type) { | ||||
if (err) setStyle = "error"; | ||||
if (type == "endPlugin") return cont(); | ||||
return pass(); | ||||
} | ||||
} | ||||
function attributes(type) { | ||||
if (type == "keyword") {setStyle = "attribute"; return cont(attributes);} | ||||
if (type == "equals") return cont(attvalue, attributes); | ||||
return pass(); | ||||
} | ||||
function attvalue(type) { | ||||
if (type == "keyword") {setStyle = "string"; return cont();} | ||||
if (type == "string") return cont(attvaluemaybe); | ||||
return pass(); | ||||
} | ||||
function attvaluemaybe(type) { | ||||
if (type == "string") return cont(attvaluemaybe); | ||||
else return pass(); | ||||
} | ||||
return { | ||||
startState: function() { | ||||
return {tokenize: inText, cc: [], indented: 0, startOfLine: true, pluginName: null, context: null}; | ||||
}, | ||||
token: function(stream, state) { | ||||
if (stream.sol()) { | ||||
state.startOfLine = true; | ||||
state.indented = stream.indentation(); | ||||
} | ||||
if (stream.eatSpace()) return null; | ||||
setStyle = type = pluginName = null; | ||||
var style = state.tokenize(stream, state); | ||||
if ((style || type) && style != "comment") { | ||||
curState = state; | ||||
while (true) { | ||||
var comb = state.cc.pop() || element; | ||||
if (comb(type || style)) break; | ||||
} | ||||
} | ||||
state.startOfLine = false; | ||||
return setStyle || style; | ||||
}, | ||||
indent: function(state, textAfter) { | ||||
var context = state.context; | ||||
if (context && context.noIndent) return 0; | ||||
if (context && /^{\//.test(textAfter)) | ||||
context = context.prev; | ||||
while (context && !context.startOfLine) | ||||
context = context.prev; | ||||
if (context) return context.indent + indentUnit; | ||||
else return 0; | ||||
}, | ||||
compareStates: function(a, b) { | ||||
if (a.indented != b.indented || a.pluginName != b.pluginName) return false; | ||||
for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) { | ||||
if (!ca || !cb) return ca == cb; | ||||
if (ca.pluginName != cb.pluginName) return false; | ||||
} | ||||
}, | ||||
electricChars: "/" | ||||
}; | ||||
}); | ||||
//I figure, why not | ||||
CodeMirror.defineMIME("text/tiki", "tiki"); | ||||