text: ﻿// 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) {
    "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");
  
  });
  
  