Implement CSS `display: flow-root` (modern clearfix)
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / External / CodeMirror / coffeescript.js
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
18 CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
19   var ERRORCLASS = "error";
20
21   function wordRegexp(words) {
22     return new RegExp("^((" + words.join(")|(") + "))\\b");
23   }
24
25   var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?|(or|and|\|\||&&|\?)=)/;
26   var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/;
27   var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/;
28   var atProp = /^@[_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", "extends"];
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
149
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     }
157
158     if (stream.match(constants)) {
159       return "atom";
160     }
161
162     if (stream.match(atProp) || state.prop && stream.match(identifiers)) {
163       return "property";
164     }
165
166     if (stream.match(keywords)) {
167       return "keyword";
168     }
169
170     if (stream.match(identifiers)) {
171       return "variable";
172     }
173
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;
191         } else {
192           stream.eat(/['"\/]/);
193         }
194       }
195       if (singleline) {
196         if (parserConf.singleLineStringErrors) {
197           outclass = ERRORCLASS;
198         } else {
199           state.tokenize = tokenBase;
200         }
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("#");
214     }
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) {
222       if (scope.type === "coffee" || scope.type == "}") {
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;
251         }
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
270     // Handle scope changes.
271     if (current === "return") {
272       state.dedent = true;
273     }
274     if (((current === "->" || current === "=>") && stream.eol())
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     }
288
289
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     }
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;
306     }
307
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},
316         prop: false,
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);
326       if (style && style != "comment") {
327         if (fillAlign) fillAlign.align = true;
328         state.prop = style == "punctuation" && stream.current() == "."
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 });
351
352 CodeMirror.defineMIME("text/x-coffeescript", "coffeescript");
353 CodeMirror.defineMIME("text/coffeescript", "coffeescript");
354
355 });