--- /dev/null
+/**
+ * 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<j; i++) {
+ if (keyFunctions[i] == str) {
+ return helpers.cont("keyword", "keyword");
+ }
+ }
+ if (/\s/.test(ch)) {
+ return null;
+ }
+ return helpers.cont("tag", "tag");
+ }
+ },
+
+ inAttribute: function(quote) {
+ return function(stream, state) {
+ var prevChar = null;
+ var currChar = null;
+ while (!stream.eol()) {
+ currChar = stream.peek();
+ if (stream.next() == quote && prevChar !== '\\') {
+ state.tokenize = parsers.smarty;
+ break;
+ }
+ prevChar = currChar;
+ }
+ return "string";
+ };
+ },
+
+ inBlock: function(style, terminator) {
+ return function(stream, state) {
+ while (!stream.eol()) {
+ if (stream.match(terminator)) {
+ state.tokenize = parsers.tokenizer;
+ break;
+ }
+ stream.next();
+ }
+ return style;
+ };
+ }
+ };
+
+
+ // the public API for CodeMirror
+ return {
+ startState: function() {
+ return {
+ tokenize: parsers.tokenizer,
+ mode: "smarty",
+ last: null,
+ depth: 0
+ };
+ },
+ token: function(stream, state) {
+ var style = state.tokenize(stream, state);
+ state.last = last;
+ return style;
+ },
+ electricChars: ""
+ };
+});
+
+CodeMirror.defineMIME("text/x-smarty", "smarty");