Web Inspector: ES6: JavaScript syntax highlighting and recognition of for..of
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / External / CodeMirror / less.js
1 /*
2   LESS mode - http://www.lesscss.org/
3   Ported to CodeMirror by Peter Kroon <plakroon@gmail.com>
4   Report bugs/issues here: https://github.com/marijnh/CodeMirror/issues
5   GitHub: @peterkroon
6 */
7
8 CodeMirror.defineMode("less", function(config) {
9   var indentUnit = config.indentUnit, type;
10   function ret(style, tp) {type = tp; return style;}
11
12   var selectors = /(^\:root$|^\:nth\-child$|^\:nth\-last\-child$|^\:nth\-of\-type$|^\:nth\-last\-of\-type$|^\:first\-child$|^\:last\-child$|^\:first\-of\-type$|^\:last\-of\-type$|^\:only\-child$|^\:only\-of\-type$|^\:empty$|^\:link|^\:visited$|^\:active$|^\:hover$|^\:focus$|^\:target$|^\:lang$|^\:enabled^\:disabled$|^\:checked$|^\:first\-line$|^\:first\-letter$|^\:before$|^\:after$|^\:not$|^\:required$|^\:invalid$)/;
13
14   function tokenBase(stream, state) {
15     var ch = stream.next();
16
17     if (ch == "@") {stream.eatWhile(/[\w\-]/); return ret("meta", stream.current());}
18     else if (ch == "/" && stream.eat("*")) {
19       state.tokenize = tokenCComment;
20       return tokenCComment(stream, state);
21     } else if (ch == "<" && stream.eat("!")) {
22       state.tokenize = tokenSGMLComment;
23       return tokenSGMLComment(stream, state);
24     } else if (ch == "=") ret(null, "compare");
25     else if (ch == "|" && stream.eat("=")) return ret(null, "compare");
26     else if (ch == "\"" || ch == "'") {
27       state.tokenize = tokenString(ch);
28       return state.tokenize(stream, state);
29     } else if (ch == "/") { // e.g.: .png will not be parsed as a class
30       if(stream.eat("/")){
31         state.tokenize = tokenSComment;
32         return tokenSComment(stream, state);
33       } else {
34         if(type == "string" || type == "(") return ret("string", "string");
35         if(state.stack[state.stack.length-1] !== undefined) return ret(null, ch);
36         stream.eatWhile(/[\a-zA-Z0-9\-_.\s]/);
37         if( /\/|\)|#/.test(stream.peek() || (stream.eatSpace() && stream.peek() === ")"))  || stream.eol() )return ret("string", "string"); // let url(/images/logo.png) without quotes return as string
38       }
39     } else if (ch == "!") {
40       stream.match(/^\s*\w*/);
41       return ret("keyword", "important");
42     } else if (/\d/.test(ch)) {
43       stream.eatWhile(/[\w.%]/);
44       return ret("number", "unit");
45     } else if (/[,+<>*\/]/.test(ch)) {
46       if(stream.peek() == "=" || type == "a")return ret("string", "string");
47       if(ch === ",")return ret(null, ch);
48       return ret(null, "select-op");
49     } else if (/[;{}:\[\]()~\|]/.test(ch)) {
50       if(ch == ":"){
51         stream.eatWhile(/[a-z\\\-]/);
52         if( selectors.test(stream.current()) ){
53           return ret("tag", "tag");
54         } else if(stream.peek() == ":"){//::-webkit-search-decoration
55           stream.next();
56           stream.eatWhile(/[a-z\\\-]/);
57           if(stream.current().match(/\:\:\-(o|ms|moz|webkit)\-/))return ret("string", "string");
58           if( selectors.test(stream.current().substring(1)) )return ret("tag", "tag");
59           return ret(null, ch);
60         } else {
61           return ret(null, ch);
62         }
63       } else if(ch == "~"){
64         if(type == "r")return ret("string", "string");
65       } else {
66         return ret(null, ch);
67       }
68     } else if (ch == ".") {
69       if(type == "(")return ret("string", "string"); // allow url(../image.png)
70       stream.eatWhile(/[\a-zA-Z0-9\-_]/);
71       if(stream.peek() === " ")stream.eatSpace();
72       if(stream.peek() === ")" || type === ":")return ret("number", "unit");//rgba(0,0,0,.25);
73       else if(stream.current().length >1){
74         if(state.stack[state.stack.length-1] === "rule" && stream.peek().match(/{|,|\+|\(/) === null)return ret("number", "unit");
75       }
76       return ret("tag", "tag");
77     } else if (ch == "#") {
78       //we don't eat white-space, we want the hex color and or id only
79       stream.eatWhile(/[A-Za-z0-9]/);
80       //check if there is a proper hex color length e.g. #eee || #eeeEEE
81       if(stream.current().length == 4 || stream.current().length == 7){
82         if(stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false) != null){//is there a valid hex color value present in the current stream
83           //when not a valid hex value, parse as id
84           if(stream.current().substring(1) != stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false))return ret("atom", "tag");
85           //eat white-space
86           stream.eatSpace();
87           //when hex value declaration doesn't end with [;,] but is does with a slash/cc comment treat it as an id, just like the other hex values that don't end with[;,]
88           if( /[\/<>.(){!$%^&*_\-\\?=+\|#'~`]/.test(stream.peek()) ){
89             if(type === "select-op")return ret("number", "unit"); else return ret("atom", "tag");
90           }
91           //#time { color: #aaa }
92           else if(stream.peek() == "}" )return ret("number", "unit");
93           //we have a valid hex color value, parse as id whenever an element/class is defined after the hex(id) value e.g. #eee aaa || #eee .aaa
94           else if( /[a-zA-Z\\]/.test(stream.peek()) )return ret("atom", "tag");
95           //when a hex value is on the end of a line, parse as id
96           else if(stream.eol())return ret("atom", "tag");
97           //default
98           else return ret("number", "unit");
99         } else {//when not a valid hexvalue in the current stream e.g. #footer
100           stream.eatWhile(/[\w\\\-]/);
101           return ret("atom", stream.current());
102         }
103       } else {//when not a valid hexvalue length
104         stream.eatWhile(/[\w\\\-]/);
105         if(state.stack[state.stack.length-1] === "rule")return ret("atom", stream.current());return ret("atom", stream.current());
106         return ret("atom", "tag");
107       }
108     } else if (ch == "&") {
109       stream.eatWhile(/[\w\-]/);
110       return ret(null, ch);
111     } else {
112       stream.eatWhile(/[\w\\\-_%.{]/);
113       if(stream.current().match(/\\/) !== null){
114         if(stream.current().charAt(stream.current().length-1) === "\\"){
115           stream.eat(/\'|\"|\)|\(/);
116           while(stream.eatWhile(/[\w\\\-_%.{]/)){
117             stream.eat(/\'|\"|\)|\(/);
118           }
119           return ret("string", stream.current());
120         }
121       } //else if(type === "tag")return ret("tag", "tag");
122         else if(type == "string"){
123         if(state.stack[state.stack.length-1] === "{" && stream.peek() === ":")return ret("variable", "variable");
124         if(stream.peek() === "/")stream.eatWhile(/[\w\\\-_%.{:\/]/);
125         return ret(type, stream.current());
126       } else if(stream.current().match(/(^http$|^https$)/) != null){
127         stream.eatWhile(/[\w\\\-_%.{:\/]/);
128         if(stream.peek() === "/")stream.eatWhile(/[\w\\\-_%.{:\/]/);
129         return ret("string", "string");
130       } else if(stream.peek() == "<" || stream.peek() == ">" || stream.peek() == "+"){
131         if(type === "(" && (stream.current() === "n" || stream.current() === "-n"))return ret("string", stream.current());
132         return ret("tag", "tag");
133       } else if( /\(/.test(stream.peek()) ){
134         if(stream.current() === "when")return ret("variable","variable");
135         else if(state.stack[state.stack.length-1] === "@media" && stream.current() === "and")return ret("variable",stream.current());
136         return ret(null, ch);
137       } else if (stream.peek() == "/" && state.stack[state.stack.length-1] !== undefined){ // url(dir/center/image.png)
138         if(stream.peek() === "/")stream.eatWhile(/[\w\\\-_%.{:\/]/);
139         return ret("string", stream.current());
140       } else if( stream.current().match(/\-\d|\-.\d/) ){ // match e.g.: -5px -0.4 etc... only colorize the minus sign
141         //commment out these 2 comment if you want the minus sign to be parsed as null -500px
142         //stream.backUp(stream.current().length-1);
143         //return ret(null, ch);
144         return ret("number", "unit");
145       } else if( /\/|[\s\)]/.test(stream.peek() || stream.eol() || (stream.eatSpace() && stream.peek() == "/")) && stream.current().indexOf(".") !== -1){
146         if(stream.current().substring(stream.current().length-1,stream.current().length) == "{"){
147           stream.backUp(1);
148           return ret("tag", "tag");
149         }//end if
150         stream.eatSpace();
151         if( /[{<>.a-zA-Z\/]/.test(stream.peek())  || stream.eol() )return ret("tag", "tag"); // e.g. button.icon-plus
152         return ret("string", "string"); // let url(/images/logo.png) without quotes return as string
153       } else if( stream.eol() || stream.peek() == "[" || stream.peek() == "#" || type == "tag" ){
154
155         if(stream.current().substring(stream.current().length-1,stream.current().length) == "{")stream.backUp(1);
156         else if(state.stack[state.stack.length-1] === "border-color" || state.stack[state.stack.length-1] === "background-position" || state.stack[state.stack.length-1] === "font-family")return ret(null, stream.current());
157         else if(type === "tag")return ret("tag", "tag");
158         else if((type === ":" || type === "unit") && state.stack[state.stack.length-1] === "rule")return ret(null, stream.current());
159         else if(state.stack[state.stack.length-1] === "rule" && type === "tag")return ret("string", stream.current());
160         else if(state.stack[state.stack.length-1] === ";" && type === ":")return ret(null, stream.current());
161         //else if(state.stack[state.stack.length-1] === ";" || type === "")return ret("variable", stream.current());
162         else if(stream.peek() === "#" && type !== undefined && type.match(/\+|,|tag|select\-op|}|{|;/g) === null)return ret("string", stream.current());
163         else if(type === "variable")return ret(null, stream.current());
164         else if(state.stack[state.stack.length-1] === "{" && type === "comment")return ret("variable", stream.current());
165         else if(state.stack.length === 0 && (type === ";" || type === "comment"))return ret("tag", stream.current());
166         else if((state.stack[state.stack.length-1] === "{" || type === ";") && state.stack[state.stack.length-1] !== "@media{")return ret("variable", stream.current());
167         else if(state.stack[state.stack.length-2] === "{" && state.stack[state.stack.length-1] === ";")return ret("variable", stream.current());
168
169         return ret("tag", "tag");
170       } else if(type == "compare" || type == "a" || type == "("){
171         return ret("string", "string");
172       } else if(type == "|" || stream.current() == "-" || type == "["){
173         if(type == "|" && stream.peek().match(/\]|=|\~/) !== null)return ret("number", stream.current());
174         else if(type == "|" )return ret("tag", "tag");
175         else if(type == "["){
176           stream.eatWhile(/\w\-/);
177           return ret("number", stream.current());
178         }
179         return ret(null, ch);
180       } else if((stream.peek() == ":") || ( stream.eatSpace() && stream.peek() == ":")) {
181         stream.next();
182         var t_v = stream.peek() == ":" ? true : false;
183         if(!t_v){
184           var old_pos = stream.pos;
185           var sc = stream.current().length;
186           stream.eatWhile(/[a-z\\\-]/);
187           var new_pos = stream.pos;
188           if(stream.current().substring(sc-1).match(selectors) != null){
189             stream.backUp(new_pos-(old_pos-1));
190             return ret("tag", "tag");
191           } else stream.backUp(new_pos-(old_pos-1));
192         } else {
193           stream.backUp(1);
194         }
195         if(t_v)return ret("tag", "tag"); else return ret("variable", "variable");
196       } else if(state.stack[state.stack.length-1]  === "font-family" || state.stack[state.stack.length-1]  === "background-position" || state.stack[state.stack.length-1]  === "border-color"){
197         return ret(null, null);
198       } else {
199
200         if(state.stack[state.stack.length-1] === null && type === ":")return ret(null, stream.current());
201
202         //else if((type === ")" && state.stack[state.stack.length-1] === "rule") || (state.stack[state.stack.length-2] === "{" && state.stack[state.stack.length-1] === "rule" && type === "variable"))return ret(null, stream.current());
203
204         else if(/\^|\$/.test(stream.current()) && stream.peek().match(/\~|=/) !== null)return ret("string", "string");//att^=val
205
206         else if(type === "unit" && state.stack[state.stack.length-1] === "rule")return ret(null, "unit");
207         else if(type === "unit" && state.stack[state.stack.length-1] === ";")return ret(null, "unit");
208         else if(type === ")" && state.stack[state.stack.length-1] === "rule")return ret(null, "unit");
209         else if(type && type.match("@") !== null  && state.stack[state.stack.length-1] === "rule")return ret(null, "unit");
210         //else if(type === "unit" && state.stack[state.stack.length-1] === "rule")return ret(null, stream.current());
211
212         else if((type === ";" || type === "}" || type === ",") && state.stack[state.stack.length-1] === ";")return ret("tag", stream.current());
213         else if((type === ";" && stream.peek() !== undefined && stream.peek().match(/{|./) === null) || (type === ";" && stream.eatSpace() && stream.peek().match(/{|./) === null))return ret("variable", stream.current());
214         else if((type === "@media" && state.stack[state.stack.length-1] === "@media") || type === "@namespace")return ret("tag", stream.current());
215
216         else if(type === "{"  && state.stack[state.stack.length-1] === ";" && stream.peek() === "{")return ret("tag", "tag");
217         else if((type === "{" || type === ":") && state.stack[state.stack.length-1] === ";")return ret(null, stream.current());
218         else if((state.stack[state.stack.length-1] === "{" && stream.eatSpace() && stream.peek().match(/.|#/) === null) || type === "select-op"  || (state.stack[state.stack.length-1] === "rule" && type === ",") )return ret("tag", "tag");
219         else if(type === "variable" && state.stack[state.stack.length-1] === "rule")return ret("tag", "tag");
220         else if((stream.eatSpace() && stream.peek() === "{") || stream.eol() || stream.peek() === "{")return ret("tag", "tag");
221         //this one messes up indentation
222         //else if((type === "}" && stream.peek() !== ":") || (type === "}" && stream.eatSpace() && stream.peek() !== ":"))return(type, "tag");
223
224         else if(type === ")" && (stream.current() == "and" || stream.current() == "and "))return ret("variable", "variable");
225         else if(type === ")" && (stream.current() == "when" || stream.current() == "when "))return ret("variable", "variable");
226         else if(type === ")" || type === "comment" || type === "{")return ret("tag", "tag");
227         else if(stream.sol())return ret("tag", "tag");
228         else if((stream.eatSpace() && stream.peek() === "#") || stream.peek() === "#")return ret("tag", "tag");
229         else if(state.stack.length === 0)return ret("tag", "tag");
230         else if(type === ";" && stream.peek() !== undefined && stream.peek().match(/^[.|\#]/g) !== null)return ret("tag", "tag");
231
232         else if(type === ":"){stream.eatSpace();return ret(null, stream.current());}
233
234         else if(stream.current() === "and " || stream.current() === "and")return ret("variable", stream.current());
235         else if(type === ";" && state.stack[state.stack.length-1] === "{")return ret("variable", stream.current());
236
237         else if(state.stack[state.stack.length-1] === "rule")return ret(null, stream.current());
238
239         return ret("tag", stream.current());
240       }
241     }
242   }
243
244   function tokenSComment(stream, state) { // SComment = Slash comment
245     stream.skipToEnd();
246     state.tokenize = tokenBase;
247     return ret("comment", "comment");
248   }
249
250   function tokenCComment(stream, state) {
251     var maybeEnd = false, ch;
252     while ((ch = stream.next()) != null) {
253       if (maybeEnd && ch == "/") {
254         state.tokenize = tokenBase;
255         break;
256       }
257       maybeEnd = (ch == "*");
258     }
259     return ret("comment", "comment");
260   }
261
262   function tokenSGMLComment(stream, state) {
263     var dashes = 0, ch;
264     while ((ch = stream.next()) != null) {
265       if (dashes >= 2 && ch == ">") {
266         state.tokenize = tokenBase;
267         break;
268       }
269       dashes = (ch == "-") ? dashes + 1 : 0;
270     }
271     return ret("comment", "comment");
272   }
273
274   function tokenString(quote) {
275     return function(stream, state) {
276       var escaped = false, ch;
277       while ((ch = stream.next()) != null) {
278         if (ch == quote && !escaped)
279           break;
280         escaped = !escaped && ch == "\\";
281       }
282       if (!escaped) state.tokenize = tokenBase;
283       return ret("string", "string");
284     };
285   }
286
287   return {
288     startState: function(base) {
289       return {tokenize: tokenBase,
290               baseIndent: base || 0,
291               stack: []};
292     },
293
294     token: function(stream, state) {
295       if (stream.eatSpace()) return null;
296       var style = state.tokenize(stream, state);
297
298       var context = state.stack[state.stack.length-1];
299       if (type == "hash" && context == "rule") style = "atom";
300       else if (style == "variable") {
301         if (context == "rule") style = null; //"tag"
302         else if (!context || context == "@media{") {
303           style = stream.current() == "when"  ? "variable" :
304           /[\s,|\s\)|\s]/.test(stream.peek()) ? "tag"      : type;
305         }
306       }
307
308       if (context == "rule" && /^[\{\};]$/.test(type))
309         state.stack.pop();
310       if (type == "{") {
311         if (context == "@media") state.stack[state.stack.length-1] = "@media{";
312         else state.stack.push("{");
313       }
314       else if (type == "}") state.stack.pop();
315       else if (type == "@media") state.stack.push("@media");
316       else if (stream.current() === "font-family") state.stack[state.stack.length-1] = "font-family";
317       else if (stream.current() === "background-position") state.stack[state.stack.length-1] = "background-position";
318       else if (stream.current() === "border-color") state.stack[state.stack.length-1] = "border-color";
319       else if (context == "{" && type != "comment" && type !== "tag") state.stack.push("rule");
320       else if (stream.peek() === ":" && stream.current().match(/@|#/) === null) style = type;
321       if(type === ";" && (state.stack[state.stack.length-1] == "font-family" || state.stack[state.stack.length-1] == "background-position" || state.stack[state.stack.length-1] == "border-color"))state.stack[state.stack.length-1] = stream.current();
322       else if(type === "tag" && stream.peek() === ")" && stream.current().match(/\:/) === null){type = null; style = null;}
323       // ????
324       else if((type === "variable" && stream.peek() === ")") || (type === "variable" && stream.eatSpace() && stream.peek() === ")"))return ret(null,stream.current());
325       return style;
326     },
327
328     indent: function(state, textAfter) {
329       var n = state.stack.length;
330       if (/^\}/.test(textAfter))
331         n -= state.stack[state.stack.length-1] === "rule" ? 2 : 1;
332       else if (state.stack[state.stack.length-2] === "{")
333         n -= state.stack[state.stack.length-1] === "rule" ? 1 : 0;
334       return state.baseIndent + n * indentUnit;
335     },
336
337     electricChars: "}",
338     blockCommentStart: "/*",
339     blockCommentEnd: "*/",
340     lineComment: "//"
341   };
342 });
343
344 CodeMirror.defineMIME("text/x-less", "less");
345 if (!CodeMirror.mimeModes.hasOwnProperty("text/css"))
346   CodeMirror.defineMIME("text/css", "less");