coffeescript.js
347 lines
| 10.7 KiB
| application/javascript
|
JavascriptLexer
r4026 | /** | |||
* 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'); | ||||