text: // CodeMirror, copyright (c) by Marijn Haverbeke and others
  // Distributed under an MIT license: http://codemirror.net/LICENSE
  
  (function (mod) {
      if (typeof exports == "object" && typeof module == "object") // CommonJS
          mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed"), require("../ruby/ruby"));
      else if (typeof define == "function" && define.amd) // AMD
          define(["../../lib/codemirror", "../htmlmixed/htmlmixed", "../ruby/ruby"], mod);
      else // Plain browser env
          mod(CodeMirror);
  })(function (CodeMirror) {
      "use strict";
  
      // full haml mode. This handled embedded ruby and html fragments too
      CodeMirror.defineMode("haml", function (config) {
          var htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"});
          var rubyMode = CodeMirror.getMode(config, "ruby");
  
          function rubyInQuote(endQuote) {
              return function (stream, state) {
                  var ch = stream.peek();
                  if (ch == endQuote && state.rubyState.tokenize.length == 1) {
                      // step out of ruby context as it seems to complete processing all the braces
                      stream.next();
                      state.tokenize = html;
                      return "closeAttributeTag";
                  } else {
                      return ruby(stream, state);
                  }
              };
          }
  
          function ruby(stream, state) {
              if (stream.match("-#")) {
                  stream.skipToEnd();
                  return "comment";
              }
              return rubyMode.token(stream, state.rubyState);
          }
  
          function html(stream, state) {
              var ch = stream.peek();
  
              // handle haml declarations. All declarations that cant be handled here
              // will be passed to html mode
              if (state.previousToken.style == "comment") {
                  if (state.indented > state.previousToken.indented) {
                      stream.skipToEnd();
                      return "commentLine";
                  }
              }
  
              if (state.startOfLine) {
                  if (ch == "!" && stream.match("!!")) {
                      stream.skipToEnd();
                      return "tag";
                  } else if (stream.match(/^%[\w:#\.]+=/)) {
                      state.tokenize = ruby;
                      return "hamlTag";
                  } else if (stream.match(/^%[\w:]+/)) {
                      return "hamlTag";
                  } else if (ch == "/") {
                      stream.skipToEnd();
                      return "comment";
                  }
              }
  
              if (state.startOfLine || state.previousToken.style == "hamlTag") {
                  if (ch == "#" || ch == ".") {
                      stream.match(/[\w-#\.]*/);
                      return "hamlAttribute";
                  }
              }
  
              // donot handle --> as valid ruby, make it HTML close comment instead
              if (state.startOfLine && !stream.match("-->", false) && (ch == "=" || ch == "-")) {
                  state.tokenize = ruby;
                  return state.tokenize(stream, state);
              }
  
              if (state.previousToken.style == "hamlTag" ||
                  state.previousToken.style == "closeAttributeTag" ||
                  state.previousToken.style == "hamlAttribute") {
                  if (ch == "(") {
                      state.tokenize = rubyInQuote(")");
                      return state.tokenize(stream, state);
                  } else if (ch == "{") {
                      if (!stream.match(/^\{%.*/)) {
                          state.tokenize = rubyInQuote("}");
                          return state.tokenize(stream, state);
                      }
                  }
              }
  
              return htmlMode.token(stream, state.htmlState);
          }
  
          return {
              // default to html mode
              startState: function () {
                  var htmlState = CodeMirror.startState(htmlMode);
                  var rubyState = CodeMirror.startState(rubyMode);
                  return {
                      htmlState: htmlState,
                      rubyState: rubyState,
                      indented: 0,
                      previousToken: {style: null, indented: 0},
                      tokenize: html
                  };
              },
  
              copyState: function (state) {
                  return {
                      htmlState: CodeMirror.copyState(htmlMode, state.htmlState),
                      rubyState: CodeMirror.copyState(rubyMode, state.rubyState),
                      indented: state.indented,
                      previousToken: state.previousToken,
                      tokenize: state.tokenize
                  };
              },
  
              token: function (stream, state) {
                  if (stream.sol()) {
                      state.indented = stream.indentation();
                      state.startOfLine = true;
                  }
                  if (stream.eatSpace()) return null;
                  var style = state.tokenize(stream, state);
                  state.startOfLine = false;
                  // dont record comment line as we only want to measure comment line with
                  // the opening comment block
                  if (style && style != "commentLine") {
                      state.previousToken = {style: style, indented: state.indented};
                  }
                  // if current state is ruby and the previous token is not `,` reset the
                  // tokenize to html
                  if (stream.eol() && state.tokenize == ruby) {
                      stream.backUp(1);
                      var ch = stream.peek();
                      stream.next();
                      if (ch && ch != ",") {
                          state.tokenize = html;
                      }
                  }
                  // reprocess some of the specific style tag when finish setting previousToken
                  if (style == "hamlTag") {
                      style = "tag";
                  } else if (style == "commentLine") {
                      style = "comment";
                  } else if (style == "hamlAttribute") {
                      style = "attribute";
                  } else if (style == "closeAttributeTag") {
                      style = null;
                  }
                  return style;
              }
          };
      }, "htmlmixed", "ruby");
  
      CodeMirror.defineMIME("text/x-haml", "haml");
  });
  