8384b3cd76b31a9e36226646b991ddf00a765861
[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 == "(" || type == "string")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() == ")")return ret("number", "unit");//rgba(0,0,0,.25);
73       return ret("tag", "tag");
74     } else if (ch == "#") {
75       //we don't eat white-space, we want the hex color and or id only
76       stream.eatWhile(/[A-Za-z0-9]/);
77       //check if there is a proper hex color length e.g. #eee || #eeeEEE
78       if(stream.current().length == 4 || stream.current().length == 7){
79         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
80           //when not a valid hex value, parse as id
81           if(stream.current().substring(1) != stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false))return ret("atom", "tag");
82           //eat white-space
83           stream.eatSpace();
84           //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[;,]
85           if( /[\/<>.(){!$%^&*_\-\\?=+\|#'~`]/.test(stream.peek()) )return ret("atom", "tag");
86           //#time { color: #aaa }
87           else if(stream.peek() == "}" )return ret("number", "unit");
88           //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
89             else if( /[a-zA-Z\\]/.test(stream.peek()) )return ret("atom", "tag");
90           //when a hex value is on the end of a line, parse as id
91               else if(stream.eol())return ret("atom", "tag");
92           //default
93                 else return ret("number", "unit");
94         } else {//when not a valid hexvalue in the current stream e.g. #footer
95           stream.eatWhile(/[\w\\\-]/);
96           return ret("atom", "tag");
97         }
98       } else {//when not a valid hexvalue length
99         stream.eatWhile(/[\w\\\-]/);
100         return ret("atom", "tag");
101       }
102     } else if (ch == "&") {
103       stream.eatWhile(/[\w\-]/);
104       return ret(null, ch);
105     } else {
106       stream.eatWhile(/[\w\\\-_%.{]/);
107       if(type == "string"){
108         return ret("string", "string");
109       } else if(stream.current().match(/(^http$|^https$)/) != null){
110         stream.eatWhile(/[\w\\\-_%.{:\/]/);
111         return ret("string", "string");
112       } else if(stream.peek() == "<" || stream.peek() == ">" || stream.peek() == "+"){
113         return ret("tag", "tag");
114       } else if( /\(/.test(stream.peek()) ){
115         return ret(null, ch);
116       } else if (stream.peek() == "/" && state.stack[state.stack.length-1] != undefined){ // url(dir/center/image.png)
117         return ret("string", "string");
118       } else if( stream.current().match(/\-\d|\-.\d/) ){ // match e.g.: -5px -0.4 etc... only colorize the minus sign
119         //commment out these 2 comment if you want the minus sign to be parsed as null -500px
120         //stream.backUp(stream.current().length-1);
121         //return ret(null, ch);
122         return ret("number", "unit");
123       } else if( /\/|[\s\)]/.test(stream.peek() || stream.eol() || (stream.eatSpace() && stream.peek() == "/")) && stream.current().indexOf(".") !== -1){
124         if(stream.current().substring(stream.current().length-1,stream.current().length) == "{"){
125           stream.backUp(1);
126           return ret("tag", "tag");
127         }//end if
128         stream.eatSpace();
129         if( /[{<>.a-zA-Z\/]/.test(stream.peek())  || stream.eol() )return ret("tag", "tag"); // e.g. button.icon-plus
130         return ret("string", "string"); // let url(/images/logo.png) without quotes return as string
131       } else if( stream.eol() || stream.peek() == "[" || stream.peek() == "#" || type == "tag" ){
132         if(stream.current().substring(stream.current().length-1,stream.current().length) == "{")stream.backUp(1);
133         return ret("tag", "tag");
134       } else if(type == "compare" || type == "a" || type == "("){
135         return ret("string", "string");
136       } else if(type == "|" || stream.current() == "-" || type == "["){
137         if(type == "|" )return ret("tag", "tag");
138         return ret(null, ch);
139       } else if(stream.peek() == ":") {
140         stream.next();
141         var t_v = stream.peek() == ":" ? true : false;
142         if(!t_v){
143           var old_pos = stream.pos;
144           var sc = stream.current().length;
145           stream.eatWhile(/[a-z\\\-]/);
146           var new_pos = stream.pos;
147           if(stream.current().substring(sc-1).match(selectors) != null){
148             stream.backUp(new_pos-(old_pos-1));
149             return ret("tag", "tag");
150           } else stream.backUp(new_pos-(old_pos-1));
151         } else {
152           stream.backUp(1);
153         }
154         if(t_v)return ret("tag", "tag"); else return ret("variable", "variable");
155       } else if(state.stack[state.stack.length-1]  === "font-family"){
156         return ret(null, null);
157       } else {
158         if(state.stack[state.stack.length-1] === "{" || type === "select-op"  || (state.stack[state.stack.length-1] === "rule" && type === ",") )return ret("tag", "tag");
159         return ret("variable", "variable");
160       }
161     }
162   }
163
164   function tokenSComment(stream, state) { // SComment = Slash comment
165     stream.skipToEnd();
166     state.tokenize = tokenBase;
167     return ret("comment", "comment");
168   }
169
170   function tokenCComment(stream, state) {
171     var maybeEnd = false, ch;
172     while ((ch = stream.next()) != null) {
173       if (maybeEnd && ch == "/") {
174         state.tokenize = tokenBase;
175         break;
176       }
177       maybeEnd = (ch == "*");
178     }
179     return ret("comment", "comment");
180   }
181
182   function tokenSGMLComment(stream, state) {
183     var dashes = 0, ch;
184     while ((ch = stream.next()) != null) {
185       if (dashes >= 2 && ch == ">") {
186         state.tokenize = tokenBase;
187         break;
188       }
189       dashes = (ch == "-") ? dashes + 1 : 0;
190     }
191     return ret("comment", "comment");
192   }
193
194   function tokenString(quote) {
195     return function(stream, state) {
196       var escaped = false, ch;
197       while ((ch = stream.next()) != null) {
198         if (ch == quote && !escaped)
199           break;
200         escaped = !escaped && ch == "\\";
201       }
202       if (!escaped) state.tokenize = tokenBase;
203       return ret("string", "string");
204     };
205   }
206
207   return {
208     startState: function(base) {
209       return {tokenize: tokenBase,
210               baseIndent: base || 0,
211               stack: []};
212     },
213
214     token: function(stream, state) {
215       if (stream.eatSpace()) return null;
216       var style = state.tokenize(stream, state);
217
218       var context = state.stack[state.stack.length-1];
219       if (type == "hash" && context == "rule") style = "atom";
220       else if (style == "variable") {
221         if (context == "rule") style = null; //"tag"
222         else if (!context || context == "@media{") {
223           style = stream.current() == "when"  ? "variable" :
224           /[\s,|\s\)|\s]/.test(stream.peek()) ? "tag"      : type;
225         }
226       }
227
228       if (context == "rule" && /^[\{\};]$/.test(type))
229         state.stack.pop();
230       if (type == "{") {
231         if (context == "@media") state.stack[state.stack.length-1] = "@media{";
232         else state.stack.push("{");
233       }
234       else if (type == "}") state.stack.pop();
235       else if (type == "@media") state.stack.push("@media");
236       else if (stream.current() === "font-family") state.stack[state.stack.length-1] = "font-family";
237       else if (context == "{" && type != "comment" && type !== "tag") state.stack.push("rule");
238       else if (stream.peek() === ":" && stream.current().match(/@|#/) === null) style = type;
239       return style;
240     },
241
242     indent: function(state, textAfter) {
243       var n = state.stack.length;
244
245       if (/^\}/.test(textAfter))
246         n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1;
247       return state.baseIndent + n * indentUnit;
248     },
249
250     electricChars: "}"
251   };
252 });
253
254 CodeMirror.defineMIME("text/x-less", "less");
255 if (!CodeMirror.mimeModes.hasOwnProperty("text/css"))
256   CodeMirror.defineMIME("text/css", "less");