Commit | Line | Data |
---|---|---|
44f4c339 | 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others |
373d1232 | 2 | // Distributed under an MIT license: https://codemirror.net/LICENSE |
44f4c339 | 3 | |
77b7b761 TD |
4 | /** |
5 | * Link to the project's GitHub page: | |
6 | * https://github.com/pickhardt/coffeescript-codemirror-mode | |
7 | */ | |
837afb80 TD |
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 | ||
44f4c339 | 18 | CodeMirror.defineMode("coffeescript", function(conf, parserConf) { |
837afb80 TD |
19 | var ERRORCLASS = "error"; |
20 | ||
21 | function wordRegexp(words) { | |
22 | return new RegExp("^((" + words.join(")|(") + "))\\b"); | |
23 | } | |
24 | ||
44f4c339 | 25 | var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?|(or|and|\|\||&&|\?)=)/; |
837afb80 TD |
26 | var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/; |
27 | var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/; | |
44f4c339 | 28 | var atProp = /^@[_A-Za-z$][_A-Za-z$0-9]*/; |
837afb80 TD |
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", | |
44f4c339 | 37 | "this", "@", "throw", "when", "until", "extends"]; |
837afb80 TD |
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"; | |
77b7b761 | 61 | } |
837afb80 TD |
62 | return null; |
63 | } else { | |
64 | if (scopeOffset > 0) { | |
65 | dedent(stream, state); | |
77b7b761 | 66 | } |
837afb80 TD |
67 | } |
68 | } | |
69 | if (stream.eatSpace()) { | |
70 | return null; | |
71 | } | |
77b7b761 | 72 | |
837afb80 | 73 | var ch = stream.peek(); |
77b7b761 | 74 | |
837afb80 TD |
75 | // Handle docco title comment (single line) |
76 | if (stream.match("####")) { | |
77 | stream.skipToEnd(); | |
78 | return "comment"; | |
79 | } | |
77b7b761 | 80 | |
837afb80 TD |
81 | // Handle multi line comments |
82 | if (stream.match("###")) { | |
83 | state.tokenize = longComment; | |
84 | return state.tokenize(stream, state); | |
85 | } | |
77b7b761 | 86 | |
837afb80 TD |
87 | // Single line comment |
88 | if (ch === "#") { | |
89 | stream.skipToEnd(); | |
90 | return "comment"; | |
91 | } | |
77b7b761 | 92 | |
837afb80 TD |
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); | |
77b7b761 | 111 | } |
837afb80 TD |
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 | } | |
77b7b761 | 132 | |
837afb80 TD |
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 | } | |
77b7b761 | 147 | |
44f4c339 AE |
148 | |
149 | ||
837afb80 TD |
150 | // Handle operators and delimiters |
151 | if (stream.match(operators) || stream.match(wordOperators)) { | |
152 | return "operator"; | |
153 | } | |
154 | if (stream.match(delimiters)) { | |
155 | return "punctuation"; | |
156 | } | |
77b7b761 | 157 | |
837afb80 TD |
158 | if (stream.match(constants)) { |
159 | return "atom"; | |
160 | } | |
77b7b761 | 161 | |
44f4c339 AE |
162 | if (stream.match(atProp) || state.prop && stream.match(identifiers)) { |
163 | return "property"; | |
164 | } | |
165 | ||
837afb80 TD |
166 | if (stream.match(keywords)) { |
167 | return "keyword"; | |
77b7b761 TD |
168 | } |
169 | ||
837afb80 TD |
170 | if (stream.match(identifiers)) { |
171 | return "variable"; | |
77b7b761 TD |
172 | } |
173 | ||
837afb80 TD |
174 | // Handle non-detected items |
175 | stream.next(); | |
176 | return ERRORCLASS; | |
177 | } | |
178 | ||
179 | function tokenFactory(delimiter, singleline, outclass) { | |
180 | return function(stream, state) { | |
181 | while (!stream.eol()) { | |
182 | stream.eatWhile(/[^'"\/\\]/); | |
183 | if (stream.eat("\\")) { | |
184 | stream.next(); | |
185 | if (singleline && stream.eol()) { | |
186 | return outclass; | |
187 | } | |
188 | } else if (stream.match(delimiter)) { | |
189 | state.tokenize = tokenBase; | |
190 | return outclass; | |
77b7b761 | 191 | } else { |
837afb80 | 192 | stream.eat(/['"\/]/); |
77b7b761 | 193 | } |
837afb80 TD |
194 | } |
195 | if (singleline) { | |
44f4c339 | 196 | if (parserConf.singleLineStringErrors) { |
837afb80 | 197 | outclass = ERRORCLASS; |
77b7b761 | 198 | } else { |
837afb80 | 199 | state.tokenize = tokenBase; |
77b7b761 | 200 | } |
837afb80 TD |
201 | } |
202 | return outclass; | |
203 | }; | |
204 | } | |
205 | ||
206 | function longComment(stream, state) { | |
207 | while (!stream.eol()) { | |
208 | stream.eatWhile(/[^#]/); | |
209 | if (stream.match("###")) { | |
210 | state.tokenize = tokenBase; | |
211 | break; | |
212 | } | |
213 | stream.eatWhile("#"); | |
77b7b761 | 214 | } |
837afb80 TD |
215 | return "comment"; |
216 | } | |
217 | ||
218 | function indent(stream, state, type) { | |
219 | type = type || "coffee"; | |
220 | var offset = 0, align = false, alignOffset = null; | |
221 | for (var scope = state.scope; scope; scope = scope.prev) { | |
44f4c339 | 222 | if (scope.type === "coffee" || scope.type == "}") { |
837afb80 TD |
223 | offset = scope.offset + conf.indentUnit; |
224 | break; | |
225 | } | |
226 | } | |
227 | if (type !== "coffee") { | |
228 | align = null; | |
229 | alignOffset = stream.column() + stream.current().length; | |
230 | } else if (state.scope.align) { | |
231 | state.scope.align = false; | |
232 | } | |
233 | state.scope = { | |
234 | offset: offset, | |
235 | type: type, | |
236 | prev: state.scope, | |
237 | align: align, | |
238 | alignOffset: alignOffset | |
239 | }; | |
240 | } | |
241 | ||
242 | function dedent(stream, state) { | |
243 | if (!state.scope.prev) return; | |
244 | if (state.scope.type === "coffee") { | |
245 | var _indent = stream.indentation(); | |
246 | var matched = false; | |
247 | for (var scope = state.scope; scope; scope = scope.prev) { | |
248 | if (_indent === scope.offset) { | |
249 | matched = true; | |
250 | break; | |
77b7b761 | 251 | } |
837afb80 TD |
252 | } |
253 | if (!matched) { | |
254 | return true; | |
255 | } | |
256 | while (state.scope.prev && state.scope.offset !== _indent) { | |
257 | state.scope = state.scope.prev; | |
258 | } | |
259 | return false; | |
260 | } else { | |
261 | state.scope = state.scope.prev; | |
262 | return false; | |
263 | } | |
264 | } | |
265 | ||
266 | function tokenLexer(stream, state) { | |
267 | var style = state.tokenize(stream, state); | |
268 | var current = stream.current(); | |
269 | ||
837afb80 TD |
270 | // Handle scope changes. |
271 | if (current === "return") { | |
44f4c339 | 272 | state.dedent = true; |
837afb80 | 273 | } |
44f4c339 | 274 | if (((current === "->" || current === "=>") && stream.eol()) |
837afb80 TD |
275 | || style === "indent") { |
276 | indent(stream, state); | |
277 | } | |
278 | var delimiter_index = "[({".indexOf(current); | |
279 | if (delimiter_index !== -1) { | |
280 | indent(stream, state, "])}".slice(delimiter_index, delimiter_index+1)); | |
281 | } | |
282 | if (indentKeywords.exec(current)){ | |
283 | indent(stream, state); | |
284 | } | |
285 | if (current == "then"){ | |
286 | dedent(stream, state); | |
287 | } | |
77b7b761 | 288 | |
77b7b761 | 289 | |
837afb80 TD |
290 | if (style === "dedent") { |
291 | if (dedent(stream, state)) { | |
292 | return ERRORCLASS; | |
293 | } | |
294 | } | |
295 | delimiter_index = "])}".indexOf(current); | |
296 | if (delimiter_index !== -1) { | |
297 | while (state.scope.type == "coffee" && state.scope.prev) | |
298 | state.scope = state.scope.prev; | |
299 | if (state.scope.type == current) | |
300 | state.scope = state.scope.prev; | |
301 | } | |
44f4c339 AE |
302 | if (state.dedent && stream.eol()) { |
303 | if (state.scope.type == "coffee" && state.scope.prev) | |
304 | state.scope = state.scope.prev; | |
305 | state.dedent = false; | |
837afb80 | 306 | } |
77b7b761 | 307 | |
837afb80 TD |
308 | return style; |
309 | } | |
310 | ||
311 | var external = { | |
312 | startState: function(basecolumn) { | |
313 | return { | |
314 | tokenize: tokenBase, | |
315 | scope: {offset:basecolumn || 0, type:"coffee", prev: null, align: false}, | |
44f4c339 | 316 | prop: false, |
837afb80 TD |
317 | dedent: 0 |
318 | }; | |
319 | }, | |
320 | ||
321 | token: function(stream, state) { | |
322 | var fillAlign = state.scope.align === null && state.scope; | |
323 | if (fillAlign && stream.sol()) fillAlign.align = false; | |
324 | ||
325 | var style = tokenLexer(stream, state); | |
44f4c339 AE |
326 | if (style && style != "comment") { |
327 | if (fillAlign) fillAlign.align = true; | |
328 | state.prop = style == "punctuation" && stream.current() == "." | |
837afb80 TD |
329 | } |
330 | ||
331 | return style; | |
332 | }, | |
333 | ||
334 | indent: function(state, text) { | |
335 | if (state.tokenize != tokenBase) return 0; | |
336 | var scope = state.scope; | |
337 | var closer = text && "])}".indexOf(text.charAt(0)) > -1; | |
338 | if (closer) while (scope.type == "coffee" && scope.prev) scope = scope.prev; | |
339 | var closes = closer && scope.type === text.charAt(0); | |
340 | if (scope.align) | |
341 | return scope.alignOffset - (closes ? 1 : 0); | |
342 | else | |
343 | return (closes ? scope.prev : scope).offset; | |
344 | }, | |
345 | ||
346 | lineComment: "#", | |
347 | fold: "indent" | |
348 | }; | |
349 | return external; | |
350 | }); | |
77b7b761 | 351 | |
373d1232 TD |
352 | // IANA registered media type |
353 | // https://www.iana.org/assignments/media-types/ | |
354 | CodeMirror.defineMIME("application/vnd.coffeescript", "coffeescript"); | |
355 | ||
837afb80 | 356 | CodeMirror.defineMIME("text/x-coffeescript", "coffeescript"); |
44f4c339 | 357 | CodeMirror.defineMIME("text/coffeescript", "coffeescript"); |
77b7b761 | 358 | |
77b7b761 | 359 | }); |