// CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: http://codemirror.net/LICENSE /** * Smarty 2 and 3 mode. */ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); else if (typeof define == "function" && define.amd) // AMD define(["../../lib/codemirror"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { "use strict"; CodeMirror.defineMode("smarty", function(config, parserConf) { var rightDelimiter = parserConf.rightDelimiter || "}"; var leftDelimiter = parserConf.leftDelimiter || "{"; var version = parserConf.version || 2; var baseMode = CodeMirror.getMode(config, parserConf.baseMode || "null"); var keyFunctions = ["debug", "extends", "function", "include", "literal"]; var regs = { operatorChars: /[+\-*&%=<>!?]/, validIdentifier: /[a-zA-Z0-9_]/, stringChar: /['"]/ }; var last; function cont(style, lastType) { last = lastType; return style; } function chain(stream, state, parser) { state.tokenize = parser; return parser(stream, state); } // Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode function doesNotCount(stream, pos) { if (pos == null) pos = stream.pos; return version === 3 && leftDelimiter == "{" && (pos == stream.string.length || /\s/.test(stream.string.charAt(pos))); } function tokenTop(stream, state) { if (stream.match(leftDelimiter, true)) { if (stream.eat("*")) { return chain(stream, state, tokenBlock("comment", "*" + rightDelimiter)); } else if (!doesNotCount(stream)) { state.depth++; state.tokenize = tokenSmarty; last = "startTag"; return "tag"; } } var token = baseMode.token(stream, state.base); var text = stream.current(); var found = text.indexOf(leftDelimiter); if (found > -1 && !doesNotCount(stream, stream.start + found + 1)) stream.backUp(text.length - found); return token; } // parsing Smarty content function tokenSmarty(stream, state) { if (stream.match(rightDelimiter, true)) { if (version === 3) { state.depth--; if (state.depth <= 0) { state.tokenize = tokenTop; } } else { state.tokenize = tokenTop; } return cont("tag", null); } if (stream.match(leftDelimiter, true)) { state.depth++; return cont("tag", "startTag"); } var ch = stream.next(); if (ch == "$") { stream.eatWhile(regs.validIdentifier); return cont("variable-2", "variable"); } else if (ch == "|") { return cont("operator", "pipe"); } else if (ch == ".") { return cont("operator", "property"); } else if (regs.stringChar.test(ch)) { state.tokenize = tokenAttribute(ch); return cont("string", "string"); } else if (regs.operatorChars.test(ch)) { stream.eatWhile(regs.operatorChars); return cont("operator", "operator"); } else if (ch == "[" || ch == "]") { return cont("bracket", "bracket"); } else if (ch == "(" || ch == ")") { return cont("bracket", "operator"); } else if (/\d/.test(ch)) { stream.eatWhile(/\d/); return cont("number", "number"); } else { if (state.last == "variable") { if (ch == "@") { stream.eatWhile(regs.validIdentifier); return cont("property", "property"); } else if (ch == "|") { stream.eatWhile(regs.validIdentifier); return cont("qualifier", "modifier"); } } else if (state.last == "pipe") { stream.eatWhile(regs.validIdentifier); return cont("qualifier", "modifier"); } else if (state.last == "whitespace") { stream.eatWhile(regs.validIdentifier); return cont("attribute", "modifier"); } if (state.last == "property") { stream.eatWhile(regs.validIdentifier); return cont("property", null); } else if (/\s/.test(ch)) { last = "whitespace"; return null; } var str = ""; if (ch != "/") { str += ch; } var c = null; while (c = stream.eat(regs.validIdentifier)) { str += c; } for (var i=0, j=keyFunctions.length; i<j; i++) { if (keyFunctions[i] == str) { return cont("keyword", "keyword"); } } if (/\s/.test(ch)) { return null; } return cont("tag", "tag"); } } function tokenAttribute(quote) { return function(stream, state) { var prevChar = null; var currChar = null; while (!stream.eol()) { currChar = stream.peek(); if (stream.next() == quote && prevChar !== '\\') { state.tokenize = tokenSmarty; break; } prevChar = currChar; } return "string"; }; } function tokenBlock(style, terminator) { return function(stream, state) { while (!stream.eol()) { if (stream.match(terminator)) { state.tokenize = tokenTop; break; } stream.next(); } return style; }; } return { startState: function() { return { base: CodeMirror.startState(baseMode), tokenize: tokenTop, last: null, depth: 0 }; }, copyState: function(state) { return { base: CodeMirror.copyState(baseMode, state.base), tokenize: state.tokenize, last: state.last, depth: state.depth }; }, innerMode: function(state) { if (state.tokenize == tokenTop) return {mode: baseMode, state: state.base}; }, token: function(stream, state) { var style = state.tokenize(stream, state); state.last = last; return style; }, blockCommentStart: leftDelimiter + "*", blockCommentEnd: "*" + rightDelimiter }; }); CodeMirror.defineMIME("text/x-smarty", "smarty"); });