: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
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
})(function(CodeMirror) {
// 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) {
if (ch == endQuote && state.rubyState.tokenize.length == 1) {
// step out of ruby context as it seems to complete processing all the braces
return "closeAttributeTag";
return ruby(stream, state);
function ruby(stream, state) {
if (stream.match("-#")) {
return rubyMode.token(stream, state.rubyState);
function html(stream, state) {
// 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) {
if (ch == "!" && stream.match("!!")) {
} else if (stream.match(/^%[\w:#\.]+=/)) {
} else if (stream.match(/^%[\w:]+/)) {
if (state.startOfLine || state.previousToken.style == "hamlTag") {
if ( ch == "#" || ch == ".") {
stream.match(/[\w-#\.]*/);
// donot handle --> as valid ruby, make it HTML close comment instead
if (state.startOfLine && !stream.match("-->", false) && (ch == "=" || ch == "-" )) {
return state.tokenize(stream, state);
if (state.previousToken.style == "hamlTag" ||
state.previousToken.style == "closeAttributeTag" ||
state.previousToken.style == "hamlAttribute") {
state.tokenize = rubyInQuote(")");
return state.tokenize(stream, state);
if (!stream.match(/^\{%.*/)) {
state.tokenize = rubyInQuote("}");
return state.tokenize(stream, state);
return htmlMode.token(stream, state.htmlState);
var htmlState = CodeMirror.startState(htmlMode);
var rubyState = CodeMirror.startState(rubyMode);
previousToken: { style: null, indented: 0},
copyState: function(state) {
htmlState : CodeMirror.copyState(htmlMode, state.htmlState),
rubyState: CodeMirror.copyState(rubyMode, state.rubyState),
indented: state.indented,
previousToken: state.previousToken,
token: function(stream, state) {
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
if (stream.eol() && state.tokenize == ruby) {
// reprocess some of the specific style tag when finish setting previousToken
if (style == "hamlTag") {
} else if (style == "commentLine") {
} else if (style == "hamlAttribute") {
} else if (style == "closeAttributeTag") {
CodeMirror.defineMIME("text/x-haml", "haml");