// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
* Link to the project's GitHub page:
* https://github.com/pickhardt/coffeescript-codemirror-mode
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
})(function(CodeMirror) {
CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
var ERRORCLASS = "error";
function wordRegexp(words) {
return new RegExp("^((" + words.join(")|(") + "))\\b");
var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?|(or|and|\|\||&&|\?)=)/;
var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/;
var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/;
var atProp = /^@[_A-Za-z$][_A-Za-z$0-9]*/;
var wordOperators = wordRegexp(["and", "or", "not",
"instanceof", "typeof"]);
var indentKeywords = ["for", "while", "loop", "if", "unless", "else",
"switch", "try", "catch", "finally", "class"];
var commonKeywords = ["break", "by", "continue", "debugger", "delete",
"do", "in", "of", "new", "return", "then",
"this", "@", "throw", "when", "until", "extends"];
var keywords = wordRegexp(indentKeywords.concat(commonKeywords));
indentKeywords = wordRegexp(indentKeywords);
var stringPrefixes = /^('{3}|\"{3}|['\"])/;
var regexPrefixes = /^(\/{3}|\/)/;
var commonConstants = ["Infinity", "NaN", "undefined", "null", "true", "false", "on", "off", "yes", "no"];
var constants = wordRegexp(commonConstants);
function tokenBase(stream, state) {
if (state.scope.align === null) state.scope.align = false;
var scopeOffset = state.scope.offset;
var lineOffset = stream.indentation();
if (lineOffset > scopeOffset && state.scope.type == "coffee") {
} else if (lineOffset < scopeOffset) {
// Handle docco title comment (single line)
if (stream.match("####")) {
// Handle multi line comments
if (stream.match("###")) {
state.tokenize = longComment;
return state.tokenize(stream, state);
// Handle number literals
if (stream.match(/^-?[0-9\.]/, false)) {
var floatLiteral = false;
if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) {
if (stream.match(/^-?\d+\.\d*/)) {
if (stream.match(/^-?\.\d+/)) {
// prevent from getting extra . on 1..
if (stream.peek() == "."){
if (stream.match(/^-?0x[0-9a-f]+/i)) {
if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) {
// Zero by itself with no other piece of number.
if (stream.match(/^-?0(?![\dx])/i)) {
if (stream.match(stringPrefixes)) {
state.tokenize = tokenFactory(stream.current(), false, "string");
return state.tokenize(stream, state);
if (stream.match(regexPrefixes)) {
if (stream.current() != "/" || stream.match(/^.*\//, false)) { // prevent highlight of division
state.tokenize = tokenFactory(stream.current(), true, "string-2");
return state.tokenize(stream, state);
// Handle operators and delimiters
if (stream.match(operators) || stream.match(wordOperators)) {
if (stream.match(delimiters)) {
if (stream.match(constants)) {
if (stream.match(atProp) || state.prop && stream.match(identifiers)) {
if (stream.match(keywords)) {
if (stream.match(identifiers)) {
// Handle non-detected items
function tokenFactory(delimiter, singleline, outclass) {
return function(stream, state) {
stream.eatWhile(/[^'"\/\\]/);
if (singleline && stream.eol()) {
} else if (stream.match(delimiter)) {
state.tokenize = tokenBase;
if (parserConf.singleLineStringErrors) {
state.tokenize = tokenBase;
function longComment(stream, state) {
if (stream.match("###")) {
state.tokenize = tokenBase;
function indent(stream, state, type) {
var offset = 0, align = false, alignOffset = null;
for (var scope = state.scope; scope; scope = scope.prev) {
if (scope.type === "coffee" || scope.type == "}") {
offset = scope.offset + conf.indentUnit;
alignOffset = stream.column() + stream.current().length;
} else if (state.scope.align) {
state.scope.align = false;
function dedent(stream, state) {
if (!state.scope.prev) return;
if (state.scope.type === "coffee") {
var _indent = stream.indentation();
for (var scope = state.scope; scope; scope = scope.prev) {
if (_indent === scope.offset) {
while (state.scope.prev && state.scope.offset !== _indent) {
state.scope = state.scope.prev;
state.scope = state.scope.prev;
function tokenLexer(stream, state) {
var style = state.tokenize(stream, state);
var current = stream.current();
if (current === "return") {
if (((current === "->" || current === "=>") && stream.eol())
var delimiter_index = "[({".indexOf(current);
if (delimiter_index !== -1) {
indent(stream, state, "])}".slice(delimiter_index, delimiter_index+1));
if (indentKeywords.exec(current)){
if (style === "dedent") {
if (dedent(stream, state)) {
delimiter_index = "])}".indexOf(current);
if (delimiter_index !== -1) {
while (state.scope.type == "coffee" && state.scope.prev)
state.scope = state.scope.prev;
if (state.scope.type == current)
state.scope = state.scope.prev;
if (state.dedent && stream.eol()) {
if (state.scope.type == "coffee" && state.scope.prev)
state.scope = state.scope.prev;
startState: function(basecolumn) {
scope: {offset:basecolumn || 0, type:"coffee", prev: null, align: false},
token: function(stream, state) {
var fillAlign = state.scope.align === null && state.scope;
if (fillAlign && stream.sol()) fillAlign.align = false;
var style = tokenLexer(stream, state);
if (style && style != "comment") {
if (fillAlign) fillAlign.align = true;
state.prop = style == "punctuation" && stream.current() == "."
indent: function(state, text) {
if (state.tokenize != tokenBase) return 0;
var closer = text && "])}".indexOf(text.charAt(0)) > -1;
if (closer) while (scope.type == "coffee" && scope.prev) scope = scope.prev;
var closes = closer && scope.type === text.charAt(0);
return scope.alignOffset - (closes ? 1 : 0);
return (closes ? scope.prev : scope).offset;
CodeMirror.defineMIME("text/x-coffeescript", "coffeescript");
CodeMirror.defineMIME("text/coffeescript", "coffeescript");