CodeMirror.defineMode("jade", function () {
  var symbol_regex1 = /^(?:~|!|%|\^|\*|\+|=|\\|:|;|,|\/|\?|&|<|>|\|)/;
  var open_paren_regex = /^(\(|\[)/;
  var close_paren_regex = /^(\)|\])/;
  var keyword_regex1 = /^(if|else|return|var|function|include|doctype|each)/;
  var keyword_regex2 = /^(#|{|}|\.)/;
  var keyword_regex3 = /^(in)/;
  var html_regex1 = /^(html|head|title|meta|link|script|body|br|div|input|span|a|img)/;
  var html_regex2 = /^(h1|h2|h3|h4|h5|p|strong|em)/;
  return {
    startState: function () {
      return {
        inString: false,
        stringType: "",
        beforeTag: true,
        justMatchedKeyword: false,
        afterParen: false
      };
    },
    token: function (stream, state) {
      //check for state changes
      if (!state.inString && ((stream.peek() == '"') || (stream.peek() == "'"))) {
        state.stringType = stream.peek();
        stream.next(); // Skip quote
        state.inString = true; // Update state
      }

      //return state
      if (state.inString) {
        if (stream.skipTo(state.stringType)) { // Quote found on this line
          stream.next(); // Skip quote
          state.inString = false; // Clear flag
        } else {
          stream.skipToEnd(); // Rest of line is string
        }
        state.justMatchedKeyword = false;
        return "string"; // Token style
      } else if (stream.sol() && stream.eatSpace()) {
        if (stream.match(keyword_regex1)) {
          state.justMatchedKeyword = true;
          stream.eatSpace();
          return "keyword";
        }
        if (stream.match(html_regex1) || stream.match(html_regex2)) {
          state.justMatchedKeyword = true;
          return "variable";
        }
      } else if (stream.sol() && stream.match(keyword_regex1)) {
        state.justMatchedKeyword = true;
        stream.eatSpace();
        return "keyword";
      } else if (stream.sol() && (stream.match(html_regex1) || stream.match(html_regex2))) {
        state.justMatchedKeyword = true;
        return "variable";
      } else if (stream.eatSpace()) {
        state.justMatchedKeyword = false;
        if (stream.match(keyword_regex3) && stream.eatSpace()) {
          state.justMatchedKeyword = true;
          return "keyword";
        }
      } else if (stream.match(symbol_regex1)) {
        state.justMatchedKeyword = false;
        return "atom";
      } else if (stream.match(open_paren_regex)) {
        state.afterParen = true;
        state.justMatchedKeyword = true;
        return "def";
      } else if (stream.match(close_paren_regex)) {
        state.afterParen = false;
        state.justMatchedKeyword = true;
        return "def";
      } else if (stream.match(keyword_regex2)) {
        state.justMatchedKeyword = true;
        return "keyword";
      } else if (stream.eatSpace()) {
        state.justMatchedKeyword = false;
      } else {
        stream.next();
        if (state.justMatchedKeyword) {
          return "property";
        } else if (state.afterParen) {
          return "property";
        }
      }
      return null;
    }
  };
});

CodeMirror.defineMIME('text/x-jade', 'jade');
