/** * Smarty 2 and 3 mode. */ CodeMirror.defineMode("smarty", function(config) { "use strict"; // our default settings; check to see if they're overridden var settings = { rightDelimiter: '}', leftDelimiter: '{', smartyVersion: 2 // for backward compatibility }; if (config.hasOwnProperty("leftDelimiter")) { settings.leftDelimiter = config.leftDelimiter; } if (config.hasOwnProperty("rightDelimiter")) { settings.rightDelimiter = config.rightDelimiter; } if (config.hasOwnProperty("smartyVersion") && config.smartyVersion === 3) { settings.smartyVersion = 3; } var keyFunctions = ["debug", "extends", "function", "include", "literal"]; var last; var regs = { operatorChars: /[+\-*&%=<>!?]/, validIdentifier: /[a-zA-Z0-9_]/, stringChar: /['"]/ }; var helpers = { cont: function(style, lastType) { last = lastType; return style; }, chain: function(stream, state, parser) { state.tokenize = parser; return parser(stream, state); } }; // our various parsers var parsers = { // the main tokenizer tokenizer: function(stream, state) { if (stream.match(settings.leftDelimiter, true)) { if (stream.eat("*")) { return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter)); } else { // Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode state.depth++; var isEol = stream.eol(); var isFollowedByWhitespace = /\s/.test(stream.peek()); if (settings.smartyVersion === 3 && settings.leftDelimiter === "{" && (isEol || isFollowedByWhitespace)) { state.depth--; return null; } else { state.tokenize = parsers.smarty; last = "startTag"; return "tag"; } } } else { stream.next(); return null; } }, // parsing Smarty content smarty: function(stream, state) { if (stream.match(settings.rightDelimiter, true)) { if (settings.smartyVersion === 3) { state.depth--; if (state.depth <= 0) { state.tokenize = parsers.tokenizer; } } else { state.tokenize = parsers.tokenizer; } return helpers.cont("tag", null); } if (stream.match(settings.leftDelimiter, true)) { state.depth++; return helpers.cont("tag", "startTag"); } var ch = stream.next(); if (ch == "$") { stream.eatWhile(regs.validIdentifier); return helpers.cont("variable-2", "variable"); } else if (ch == "|") { return helpers.cont("operator", "pipe"); } else if (ch == ".") { return helpers.cont("operator", "property"); } else if (regs.stringChar.test(ch)) { state.tokenize = parsers.inAttribute(ch); return helpers.cont("string", "string"); } else if (regs.operatorChars.test(ch)) { stream.eatWhile(regs.operatorChars); return helpers.cont("operator", "operator"); } else if (ch == "[" || ch == "]") { return helpers.cont("bracket", "bracket"); } else if (ch == "(" || ch == ")") { return helpers.cont("bracket", "operator"); } else if (/\d/.test(ch)) { stream.eatWhile(/\d/); return helpers.cont("number", "number"); } else { if (state.last == "variable") { if (ch == "@") { stream.eatWhile(regs.validIdentifier); return helpers.cont("property", "property"); } else if (ch == "|") { stream.eatWhile(regs.validIdentifier); return helpers.cont("qualifier", "modifier"); } } else if (state.last == "pipe") { stream.eatWhile(regs.validIdentifier); return helpers.cont("qualifier", "modifier"); } else if (state.last == "whitespace") { stream.eatWhile(regs.validIdentifier); return helpers.cont("attribute", "modifier"); } if (state.last == "property") { stream.eatWhile(regs.validIdentifier); return helpers.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