/** * @file smartymixed.js * @brief Smarty Mixed Codemirror mode (Smarty + Mixed HTML) * @author Ruslan Osmanov * @version 3.0 * @date 05.07.2013 */ CodeMirror.defineMode("smartymixed", function(config) { var settings, regs, helpers, parsers, htmlMixedMode = CodeMirror.getMode(config, "htmlmixed"), smartyMode = CodeMirror.getMode(config, "smarty"), settings = { rightDelimiter: '}', leftDelimiter: '{' }; if (config.hasOwnProperty("leftDelimiter")) { settings.leftDelimiter = config.leftDelimiter; } if (config.hasOwnProperty("rightDelimiter")) { settings.rightDelimiter = config.rightDelimiter; } regs = { smartyComment: new RegExp("^" + settings.leftDelimiter + "\\*"), literalOpen: new RegExp(settings.leftDelimiter + "literal" + settings.rightDelimiter), literalClose: new RegExp(settings.leftDelimiter + "\/literal" + settings.rightDelimiter), hasLeftDelimeter: new RegExp(".*" + settings.leftDelimiter), htmlHasLeftDelimeter: new RegExp("[^<>]*" + settings.leftDelimiter) }; helpers = { chain: function(stream, state, parser) { state.tokenize = parser; return parser(stream, state); }, cleanChain: function(stream, state, parser) { state.tokenize = null; state.localState = null; state.localMode = null; return (typeof parser == "string") ? (parser ? parser : null) : parser(stream, state); }, maybeBackup: function(stream, pat, style) { var cur = stream.current(); var close = cur.search(pat), m; if (close > - 1) stream.backUp(cur.length - close); else if (m = cur.match(/<\/?$/)) { stream.backUp(cur.length); if (!stream.match(pat, false)) stream.match(cur[0]); } return style; } }; parsers = { html: function(stream, state) { if (!state.inLiteral && stream.match(regs.htmlHasLeftDelimeter, false)) { state.tokenize = parsers.smarty; state.localMode = smartyMode; state.localState = smartyMode.startState(htmlMixedMode.indent(state.htmlMixedState, "")); return helpers.maybeBackup(stream, settings.leftDelimiter, smartyMode.token(stream, state.localState)); } return htmlMixedMode.token(stream, state.htmlMixedState); }, smarty: function(stream, state) { if (stream.match(settings.leftDelimiter, false)) { if (stream.match(regs.smartyComment, false)) { return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter)); } } else if (stream.match(settings.rightDelimiter, false)) { stream.eat(settings.rightDelimiter); state.tokenize = parsers.html; state.localMode = htmlMixedMode; state.localState = state.htmlMixedState; return "tag"; } return helpers.maybeBackup(stream, settings.rightDelimiter, smartyMode.token(stream, state.localState)); }, inBlock: function(style, terminator) { return function(stream, state) { while (!stream.eol()) { if (stream.match(terminator)) { helpers.cleanChain(stream, state, ""); break; } stream.next(); } return style; }; } }; return { startState: function() { var state = htmlMixedMode.startState(); return { token: parsers.html, localMode: null, localState: null, htmlMixedState: state, tokenize: null, inLiteral: false }; }, copyState: function(state) { var local = null, tok = (state.tokenize || state.token); if (state.localState) { local = CodeMirror.copyState((tok != parsers.html ? smartyMode : htmlMixedMode), state.localState); } return { token: state.token, tokenize: state.tokenize, localMode: state.localMode, localState: local, htmlMixedState: CodeMirror.copyState(htmlMixedMode, state.htmlMixedState), inLiteral: state.inLiteral }; }, token: function(stream, state) { if (stream.match(settings.leftDelimiter, false)) { if (!state.inLiteral && stream.match(regs.literalOpen, true)) { state.inLiteral = true; return "keyword"; } else if (state.inLiteral && stream.match(regs.literalClose, true)) { state.inLiteral = false; return "keyword"; } } if (state.inLiteral && state.localState != state.htmlMixedState) { state.tokenize = parsers.html; state.localMode = htmlMixedMode; state.localState = state.htmlMixedState; } var style = (state.tokenize || state.token)(stream, state); return style; }, indent: function(state, textAfter) { if (state.localMode == smartyMode || (state.inLiteral && !state.localMode) || regs.hasLeftDelimeter.test(textAfter)) { return CodeMirror.Pass; } return htmlMixedMode.indent(state.htmlMixedState, textAfter); }, electricChars: "/{}:", innerMode: function(state) { return { state: state.localState || state.htmlMixedState, mode: state.localMode || htmlMixedMode }; } }; }, "htmlmixed"); CodeMirror.defineMIME("text/x-smarty", "smartymixed"); // vim: et ts=2 sts=2 sw=2