1// CodeMirror, copyright (c) by Marijn Haverbeke and others 2// Distributed under an MIT license: http://codemirror.net/LICENSE 3 4/** 5 * Link to the project's GitHub page: 6 * https://github.com/pickhardt/coffeescript-codemirror-mode 7 */ 8(function(mod) { 9 if (typeof exports == "object" && typeof module == "object") // CommonJS 10 mod(require("../../lib/codemirror")); 11 else if (typeof define == "function" && define.amd) // AMD 12 define(["../../lib/codemirror"], mod); 13 else // Plain browser env 14 mod(CodeMirror); 15})(function(CodeMirror) { 16"use strict"; 17 18CodeMirror.defineMode("coffeescript", function(conf) { 19 var ERRORCLASS = "error"; 20 21 function wordRegexp(words) { 22 return new RegExp("^((" + words.join(")|(") + "))\\b"); 23 } 24 25 var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?)/; 26 var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/; 27 var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/; 28 var properties = /^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*/; 29 30 var wordOperators = wordRegexp(["and", "or", "not", 31 "is", "isnt", "in", 32 "instanceof", "typeof"]); 33 var indentKeywords = ["for", "while", "loop", "if", "unless", "else", 34 "switch", "try", "catch", "finally", "class"]; 35 var commonKeywords = ["break", "by", "continue", "debugger", "delete", 36 "do", "in", "of", "new", "return", "then", 37 "this", "throw", "when", "until"]; 38 39 var keywords = wordRegexp(indentKeywords.concat(commonKeywords)); 40 41 indentKeywords = wordRegexp(indentKeywords); 42 43 44 var stringPrefixes = /^('{3}|\"{3}|['\"])/; 45 var regexPrefixes = /^(\/{3}|\/)/; 46 var commonConstants = ["Infinity", "NaN", "undefined", "null", "true", "false", "on", "off", "yes", "no"]; 47 var constants = wordRegexp(commonConstants); 48 49 // Tokenizers 50 function tokenBase(stream, state) { 51 // Handle scope changes 52 if (stream.sol()) { 53 if (state.scope.align === null) state.scope.align = false; 54 var scopeOffset = state.scope.offset; 55 if (stream.eatSpace()) { 56 var lineOffset = stream.indentation(); 57 if (lineOffset > scopeOffset && state.scope.type == "coffee") { 58 return "indent"; 59 } else if (lineOffset < scopeOffset) { 60 return "dedent"; 61 } 62 return null; 63 } else { 64 if (scopeOffset > 0) { 65 dedent(stream, state); 66 } 67 } 68 } 69 if (stream.eatSpace()) { 70 return null; 71 } 72 73 var ch = stream.peek(); 74 75 // Handle docco title comment (single line) 76 if (stream.match("####")) { 77 stream.skipToEnd(); 78 return "comment"; 79 } 80 81 // Handle multi line comments 82 if (stream.match("###")) { 83 state.tokenize = longComment; 84 return state.tokenize(stream, state); 85 } 86 87 // Single line comment 88 if (ch === "#") { 89 stream.skipToEnd(); 90 return "comment"; 91 } 92 93 // Handle number literals 94 if (stream.match(/^-?[0-9\.]/, false)) { 95 var floatLiteral = false; 96 // Floats 97 if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) { 98 floatLiteral = true; 99 } 100 if (stream.match(/^-?\d+\.\d*/)) { 101 floatLiteral = true; 102 } 103 if (stream.match(/^-?\.\d+/)) { 104 floatLiteral = true; 105 } 106 107 if (floatLiteral) { 108 // prevent from getting extra . on 1.. 109 if (stream.peek() == "."){ 110 stream.backUp(1); 111 } 112 return "number"; 113 } 114 // Integers 115 var intLiteral = false; 116 // Hex 117 if (stream.match(/^-?0x[0-9a-f]+/i)) { 118 intLiteral = true; 119 } 120 // Decimal 121 if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) { 122 intLiteral = true; 123 } 124 // Zero by itself with no other piece of number. 125 if (stream.match(/^-?0(?![\dx])/i)) { 126 intLiteral = true; 127 } 128 if (intLiteral) { 129 return "number"; 130 } 131 } 132 133 // Handle strings 134 if (stream.match(stringPrefixes)) { 135 state.tokenize = tokenFactory(stream.current(), false, "string"); 136 return state.tokenize(stream, state); 137 } 138 // Handle regex literals 139 if (stream.match(regexPrefixes)) { 140 if (stream.current() != "/" || stream.match(/^.*\//, false)) { // prevent highlight of division 141 state.tokenize = tokenFactory(stream.current(), true, "string-2"); 142 return state.tokenize(stream, state); 143 } else { 144 stream.backUp(1); 145 } 146 } 147 148 // Handle operators and delimiters 149 if (stream.match(operators) || stream.match(wordOperators)) { 150 return "operator"; 151 } 152 if (stream.match(delimiters)) { 153 return "punctuation"; 154 } 155 156 if (stream.match(constants)) { 157 return "atom"; 158 } 159 160 if (stream.match(keywords)) { 161 return "keyword"; 162 } 163 164 if (stream.match(identifiers)) { 165 return "variable"; 166 } 167 168 if (stream.match(properties)) { 169 return "property"; 170 } 171 172 // Handle non-detected items 173 stream.next(); 174 return ERRORCLASS; 175 } 176 177 function tokenFactory(delimiter, singleline, outclass) { 178 return function(stream, state) { 179 while (!stream.eol()) { 180 stream.eatWhile(/[^'"\/\\]/); 181 if (stream.eat("\\")) { 182 stream.next(); 183 if (singleline && stream.eol()) { 184 return outclass; 185 } 186 } else if (stream.match(delimiter)) { 187 state.tokenize = tokenBase; 188 return outclass; 189 } else { 190 stream.eat(/['"\/]/); 191 } 192 } 193 if (singleline) { 194 if (conf.mode.singleLineStringErrors) { 195 outclass = ERRORCLASS; 196 } else { 197 state.tokenize = tokenBase; 198 } 199 } 200 return outclass; 201 }; 202 } 203 204 function longComment(stream, state) { 205 while (!stream.eol()) { 206 stream.eatWhile(/[^#]/); 207 if (stream.match("###")) { 208 state.tokenize = tokenBase; 209 break; 210 } 211 stream.eatWhile("#"); 212 } 213 return "comment"; 214 } 215 216 function indent(stream, state, type) { 217 type = type || "coffee"; 218 var offset = 0, align = false, alignOffset = null; 219 for (var scope = state.scope; scope; scope = scope.prev) { 220 if (scope.type === "coffee") { 221 offset = scope.offset + conf.indentUnit; 222 break; 223 } 224 } 225 if (type !== "coffee") { 226 align = null; 227 alignOffset = stream.column() + stream.current().length; 228 } else if (state.scope.align) { 229 state.scope.align = false; 230 } 231 state.scope = { 232 offset: offset, 233 type: type, 234 prev: state.scope, 235 align: align, 236 alignOffset: alignOffset 237 }; 238 } 239 240 function dedent(stream, state) { 241 if (!state.scope.prev) return; 242 if (state.scope.type === "coffee") { 243 var _indent = stream.indentation(); 244 var matched = false; 245 for (var scope = state.scope; scope; scope = scope.prev) { 246 if (_indent === scope.offset) { 247 matched = true; 248 break; 249 } 250 } 251 if (!matched) { 252 return true; 253 } 254 while (state.scope.prev && state.scope.offset !== _indent) { 255 state.scope = state.scope.prev; 256 } 257 return false; 258 } else { 259 state.scope = state.scope.prev; 260 return false; 261 } 262 } 263 264 function tokenLexer(stream, state) { 265 var style = state.tokenize(stream, state); 266 var current = stream.current(); 267 268 // Handle "." connected identifiers 269 if (current === ".") { 270 style = state.tokenize(stream, state); 271 current = stream.current(); 272 if (/^\.[\w$]+$/.test(current)) { 273 return "variable"; 274 } else { 275 return ERRORCLASS; 276 } 277 } 278 279 // Handle scope changes. 280 if (current === "return") { 281 state.dedent += 1; 282 } 283 if (((current === "->" || current === "=>") && 284 !state.lambda && 285 !stream.peek()) 286 || style === "indent") { 287 indent(stream, state); 288 } 289 var delimiter_index = "[({".indexOf(current); 290 if (delimiter_index !== -1) { 291 indent(stream, state, "])}".slice(delimiter_index, delimiter_index+1)); 292 } 293 if (indentKeywords.exec(current)){ 294 indent(stream, state); 295 } 296 if (current == "then"){ 297 dedent(stream, state); 298 } 299 300 301 if (style === "dedent") { 302 if (dedent(stream, state)) { 303 return ERRORCLASS; 304 } 305 } 306 delimiter_index = "])}".indexOf(current); 307 if (delimiter_index !== -1) { 308 while (state.scope.type == "coffee" && state.scope.prev) 309 state.scope = state.scope.prev; 310 if (state.scope.type == current) 311 state.scope = state.scope.prev; 312 } 313 if (state.dedent > 0 && stream.eol() && state.scope.type == "coffee") { 314 if (state.scope.prev) state.scope = state.scope.prev; 315 state.dedent -= 1; 316 } 317 318 return style; 319 } 320 321 var external = { 322 startState: function(basecolumn) { 323 return { 324 tokenize: tokenBase, 325 scope: {offset:basecolumn || 0, type:"coffee", prev: null, align: false}, 326 lastToken: null, 327 lambda: false, 328 dedent: 0 329 }; 330 }, 331 332 token: function(stream, state) { 333 var fillAlign = state.scope.align === null && state.scope; 334 if (fillAlign && stream.sol()) fillAlign.align = false; 335 336 var style = tokenLexer(stream, state); 337 if (fillAlign && style && style != "comment") fillAlign.align = true; 338 339 state.lastToken = {style:style, content: stream.current()}; 340 341 if (stream.eol() && stream.lambda) { 342 state.lambda = false; 343 } 344 345 return style; 346 }, 347 348 indent: function(state, text) { 349 if (state.tokenize != tokenBase) return 0; 350 var scope = state.scope; 351 var closer = text && "])}".indexOf(text.charAt(0)) > -1; 352 if (closer) while (scope.type == "coffee" && scope.prev) scope = scope.prev; 353 var closes = closer && scope.type === text.charAt(0); 354 if (scope.align) 355 return scope.alignOffset - (closes ? 1 : 0); 356 else 357 return (closes ? scope.prev : scope).offset; 358 }, 359 360 lineComment: "#", 361 fold: "indent" 362 }; 363 return external; 364}); 365 366CodeMirror.defineMIME("text/x-coffeescript", "coffeescript"); 367 368}); 369