| /** |
| * Link to the project's GitHub page: |
| * https://github.com/pickhardt/coffeescript-codemirror-mode |
| */ |
| CodeMirror.defineMode('coffeescript', function(conf) { |
| var ERRORCLASS = 'error'; |
| |
| function wordRegexp(words) { |
| return new RegExp("^((" + words.join(")|(") + "))\\b"); |
| } |
| |
| var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\?]"); |
| var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\},:`=;\\.]'); |
| var doubleOperators = new RegExp("^((\->)|(\=>)|(\\+\\+)|(\\+\\=)|(\\-\\-)|(\\-\\=)|(\\*\\*)|(\\*\\=)|(\\/\\/)|(\\/\\=)|(==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//))"); |
| var doubleDelimiters = new RegExp("^((\\.\\.)|(\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); |
| var tripleDelimiters = new RegExp("^((\\.\\.\\.)|(//=)|(>>=)|(<<=)|(\\*\\*=))"); |
| var identifiers = new RegExp("^[_A-Za-z$][_A-Za-z$0-9]*"); |
| var properties = new RegExp("^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*"); |
| |
| var wordOperators = wordRegexp(['and', 'or', 'not', |
| 'is', 'isnt', 'in', |
| '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']; |
| |
| var keywords = wordRegexp(indentKeywords.concat(commonKeywords)); |
| |
| indentKeywords = wordRegexp(indentKeywords); |
| |
| |
| var stringPrefixes = new RegExp("^('{3}|\"{3}|['\"])"); |
| var regexPrefixes = new RegExp("^(/{3}|/)"); |
| var commonConstants = ['Infinity', 'NaN', 'undefined', 'null', 'true', 'false', 'on', 'off', 'yes', 'no']; |
| var constants = wordRegexp(commonConstants); |
| |
| // Tokenizers |
| function tokenBase(stream, state) { |
| // Handle scope changes |
| if (stream.sol()) { |
| var scopeOffset = state.scopes[0].offset; |
| if (stream.eatSpace()) { |
| var lineOffset = stream.indentation(); |
| if (lineOffset > scopeOffset) { |
| return 'indent'; |
| } else if (lineOffset < scopeOffset) { |
| return 'dedent'; |
| } |
| return null; |
| } else { |
| if (scopeOffset > 0) { |
| dedent(stream, state); |
| } |
| } |
| } |
| if (stream.eatSpace()) { |
| return null; |
| } |
| |
| var ch = stream.peek(); |
| |
| // Handle docco title comment (single line) |
| if (stream.match("####")) { |
| stream.skipToEnd(); |
| return 'comment'; |
| } |
| |
| // Handle multi line comments |
| if (stream.match("###")) { |
| state.tokenize = longComment; |
| return state.tokenize(stream, state); |
| } |
| |
| // Single line comment |
| if (ch === '#') { |
| stream.skipToEnd(); |
| return 'comment'; |
| } |
| |
| // Handle number literals |
| if (stream.match(/^-?[0-9\.]/, false)) { |
| var floatLiteral = false; |
| // Floats |
| if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) { |
| floatLiteral = true; |
| } |
| if (stream.match(/^-?\d+\.\d*/)) { |
| floatLiteral = true; |
| } |
| if (stream.match(/^-?\.\d+/)) { |
| floatLiteral = true; |
| } |
| |
| if (floatLiteral) { |
| // prevent from getting extra . on 1.. |
| if (stream.peek() == "."){ |
| stream.backUp(1); |
| } |
| return 'number'; |
| } |
| // Integers |
| var intLiteral = false; |
| // Hex |
| if (stream.match(/^-?0x[0-9a-f]+/i)) { |
| intLiteral = true; |
| } |
| // Decimal |
| if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) { |
| intLiteral = true; |
| } |
| // Zero by itself with no other piece of number. |
| if (stream.match(/^-?0(?![\dx])/i)) { |
| intLiteral = true; |
| } |
| if (intLiteral) { |
| return 'number'; |
| } |
| } |
| |
| // Handle strings |
| if (stream.match(stringPrefixes)) { |
| state.tokenize = tokenFactory(stream.current(), 'string'); |
| return state.tokenize(stream, state); |
| } |
| // Handle regex literals |
| if (stream.match(regexPrefixes)) { |
| if (stream.current() != '/' || stream.match(/^.*\//, false)) { // prevent highlight of division |
| state.tokenize = tokenFactory(stream.current(), 'string-2'); |
| return state.tokenize(stream, state); |
| } else { |
| stream.backUp(1); |
| } |
| } |
| |
| // Handle operators and delimiters |
| if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { |
| return 'punctuation'; |
| } |
| if (stream.match(doubleOperators) |
| || stream.match(singleOperators) |
| || stream.match(wordOperators)) { |
| return 'operator'; |
| } |
| if (stream.match(singleDelimiters)) { |
| return 'punctuation'; |
| } |
| |
| if (stream.match(constants)) { |
| return 'atom'; |
| } |
| |
| if (stream.match(keywords)) { |
| return 'keyword'; |
| } |
| |
| if (stream.match(identifiers)) { |
| return 'variable'; |
| } |
| |
| if (stream.match(properties)) { |
| return 'property'; |
| } |
| |
| // Handle non-detected items |
| stream.next(); |
| return ERRORCLASS; |
| } |
| |
| function tokenFactory(delimiter, outclass) { |
| var singleline = delimiter.length == 1; |
| return function(stream, state) { |
| while (!stream.eol()) { |
| stream.eatWhile(/[^'"\/\\]/); |
| if (stream.eat('\\')) { |
| stream.next(); |
| if (singleline && stream.eol()) { |
| return outclass; |
| } |
| } else if (stream.match(delimiter)) { |
| state.tokenize = tokenBase; |
| return outclass; |
| } else { |
| stream.eat(/['"\/]/); |
| } |
| } |
| if (singleline) { |
| if (conf.mode.singleLineStringErrors) { |
| outclass = ERRORCLASS; |
| } else { |
| state.tokenize = tokenBase; |
| } |
| } |
| return outclass; |
| }; |
| } |
| |
| function longComment(stream, state) { |
| while (!stream.eol()) { |
| stream.eatWhile(/[^#]/); |
| if (stream.match("###")) { |
| state.tokenize = tokenBase; |
| break; |
| } |
| stream.eatWhile("#"); |
| } |
| return "comment"; |
| } |
| |
| function indent(stream, state, type) { |
| type = type || 'coffee'; |
| var indentUnit = 0; |
| if (type === 'coffee') { |
| for (var i = 0; i < state.scopes.length; i++) { |
| if (state.scopes[i].type === 'coffee') { |
| indentUnit = state.scopes[i].offset + conf.indentUnit; |
| break; |
| } |
| } |
| } else { |
| indentUnit = stream.column() + stream.current().length; |
| } |
| state.scopes.unshift({ |
| offset: indentUnit, |
| type: type |
| }); |
| } |
| |
| function dedent(stream, state) { |
| if (state.scopes.length == 1) return; |
| if (state.scopes[0].type === 'coffee') { |
| var _indent = stream.indentation(); |
| var _indent_index = -1; |
| for (var i = 0; i < state.scopes.length; ++i) { |
| if (_indent === state.scopes[i].offset) { |
| _indent_index = i; |
| break; |
| } |
| } |
| if (_indent_index === -1) { |
| return true; |
| } |
| while (state.scopes[0].offset !== _indent) { |
| state.scopes.shift(); |
| } |
| return false; |
| } else { |
| state.scopes.shift(); |
| return false; |
| } |
| } |
| |
| function tokenLexer(stream, state) { |
| var style = state.tokenize(stream, state); |
| var current = stream.current(); |
| |
| // Handle '.' connected identifiers |
| if (current === '.') { |
| style = state.tokenize(stream, state); |
| current = stream.current(); |
| if (style === 'variable') { |
| return 'variable'; |
| } else { |
| return ERRORCLASS; |
| } |
| } |
| |
| // Handle scope changes. |
| if (current === 'return') { |
| state.dedent += 1; |
| } |
| if (((current === '->' || current === '=>') && |
| !state.lambda && |
| state.scopes[0].type == 'coffee' && |
| stream.peek() === '') |
| || style === 'indent') { |
| indent(stream, state); |
| } |
| var delimiter_index = '[({'.indexOf(current); |
| if (delimiter_index !== -1) { |
| indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1)); |
| } |
| if (indentKeywords.exec(current)){ |
| indent(stream, state); |
| } |
| if (current == 'then'){ |
| dedent(stream, state); |
| } |
| |
| |
| if (style === 'dedent') { |
| if (dedent(stream, state)) { |
| return ERRORCLASS; |
| } |
| } |
| delimiter_index = '])}'.indexOf(current); |
| if (delimiter_index !== -1) { |
| if (dedent(stream, state)) { |
| return ERRORCLASS; |
| } |
| } |
| if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'coffee') { |
| if (state.scopes.length > 1) state.scopes.shift(); |
| state.dedent -= 1; |
| } |
| |
| return style; |
| } |
| |
| var external = { |
| startState: function(basecolumn) { |
| return { |
| tokenize: tokenBase, |
| scopes: [{offset:basecolumn || 0, type:'coffee'}], |
| lastToken: null, |
| lambda: false, |
| dedent: 0 |
| }; |
| }, |
| |
| token: function(stream, state) { |
| var style = tokenLexer(stream, state); |
| |
| state.lastToken = {style:style, content: stream.current()}; |
| |
| if (stream.eol() && stream.lambda) { |
| state.lambda = false; |
| } |
| |
| return style; |
| }, |
| |
| indent: function(state) { |
| if (state.tokenize != tokenBase) { |
| return 0; |
| } |
| |
| return state.scopes[0].offset; |
| }, |
| |
| lineComment: "#" |
| }; |
| return external; |
| }); |
| |
| CodeMirror.defineMIME('text/x-coffeescript', 'coffeescript'); |