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