Fix: merge conflict
[myslice.git] / third-party / codemirror-3.15 / mode / less / 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  GitHub: @peterkroon
5 */
6
7 CodeMirror.defineMode("less", function(config) {
8   var indentUnit = config.indentUnit, type;
9   function ret(style, tp) {type = tp; return style;}
10
11   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$)/;
12
13   function tokenBase(stream, state) {
14     var ch = stream.next();
15
16     if (ch == "@") {stream.eatWhile(/[\w\-]/); return ret("meta", stream.current());}
17     else if (ch == "/" && stream.eat("*")) {
18       state.tokenize = tokenCComment;
19       return tokenCComment(stream, state);
20     }
21     else if (ch == "<" && stream.eat("!")) {
22       state.tokenize = tokenSGMLComment;
23       return tokenSGMLComment(stream, state);
24     }
25     else if (ch == "=") ret(null, "compare");
26     else if (ch == "|" && stream.eat("=")) return ret(null, "compare");
27     else if (ch == "\"" || ch == "'") {
28       state.tokenize = tokenString(ch);
29       return state.tokenize(stream, state);
30     }
31     else if (ch == "/") { // e.g.: .png will not be parsed as a class
32       if(stream.eat("/")){
33         state.tokenize = tokenSComment;
34         return tokenSComment(stream, state);
35       }else{
36         if(type == "string" || type == "(")return ret("string", "string");
37         if(state.stack[state.stack.length-1] != undefined)return ret(null, ch);
38         stream.eatWhile(/[\a-zA-Z0-9\-_.\s]/);
39         if( /\/|\)|#/.test(stream.peek() || (stream.eatSpace() && stream.peek() == ")"))  || stream.eol() )return ret("string", "string"); // let url(/images/logo.png) without quotes return as string
40       }
41     }
42     else if (ch == "!") {
43       stream.match(/^\s*\w*/);
44       return ret("keyword", "important");
45     }
46     else if (/\d/.test(ch)) {
47       stream.eatWhile(/[\w.%]/);
48       return ret("number", "unit");
49     }
50     else if (/[,+<>*\/]/.test(ch)) {
51       if(stream.peek() == "=" || type == "a")return ret("string", "string");
52       return ret(null, "select-op");
53     }
54     else if (/[;{}:\[\]()~\|]/.test(ch)) {
55       if(ch == ":"){
56         stream.eatWhile(/[a-z\\\-]/);
57         if( selectors.test(stream.current()) ){
58           return ret("tag", "tag");
59         }else if(stream.peek() == ":"){//::-webkit-search-decoration
60           stream.next();
61           stream.eatWhile(/[a-z\\\-]/);
62           if(stream.current().match(/\:\:\-(o|ms|moz|webkit)\-/))return ret("string", "string");
63           if( selectors.test(stream.current().substring(1)) )return ret("tag", "tag");
64           return ret(null, ch);
65         }else{
66           return ret(null, ch);
67         }
68       }else if(ch == "~"){
69         if(type == "r")return ret("string", "string");
70       }else{
71         return ret(null, ch);
72       }
73     }
74     else if (ch == ".") {
75       if(type == "(" || type == "string")return ret("string", "string"); // allow url(../image.png)
76       stream.eatWhile(/[\a-zA-Z0-9\-_]/);
77       if(stream.peek() == " ")stream.eatSpace();
78       if(stream.peek() == ")")return ret("number", "unit");//rgba(0,0,0,.25);
79       return ret("tag", "tag");
80     }
81     else if (ch == "#") {
82       //we don't eat white-space, we want the hex color and or id only
83       stream.eatWhile(/[A-Za-z0-9]/);
84       //check if there is a proper hex color length e.g. #eee || #eeeEEE
85       if(stream.current().length == 4 || stream.current().length == 7){
86         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
87           //when not a valid hex value, parse as id
88           if(stream.current().substring(1) != stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false))return ret("atom", "tag");
89           //eat white-space
90           stream.eatSpace();
91           //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[;,]
92           if( /[\/<>.(){!$%^&*_\-\\?=+\|#'~`]/.test(stream.peek()) )return ret("atom", "tag");
93           //#time { color: #aaa }
94           else if(stream.peek() == "}" )return ret("number", "unit");
95           //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
96           else if( /[a-zA-Z\\]/.test(stream.peek()) )return ret("atom", "tag");
97           //when a hex value is on the end of a line, parse as id
98           else if(stream.eol())return ret("atom", "tag");
99           //default
100           else return ret("number", "unit");
101         }else{//when not a valid hexvalue in the current stream e.g. #footer
102           stream.eatWhile(/[\w\\\-]/);
103           return ret("atom", "tag");
104         }
105       }else{//when not a valid hexvalue length
106         stream.eatWhile(/[\w\\\-]/);
107         return ret("atom", "tag");
108       }
109     }
110     else if (ch == "&") {
111       stream.eatWhile(/[\w\-]/);
112       return ret(null, ch);
113     }
114     else {
115       stream.eatWhile(/[\w\\\-_%.{]/);
116       if(type == "string"){
117         return ret("string", "string");
118       }else if(stream.current().match(/(^http$|^https$)/) != null){
119         stream.eatWhile(/[\w\\\-_%.{:\/]/);
120         return ret("string", "string");
121       }else if(stream.peek() == "<" || stream.peek() == ">"){
122         return ret("tag", "tag");
123       }else if( /\(/.test(stream.peek()) ){
124         return ret(null, ch);
125       }else if (stream.peek() == "/" && state.stack[state.stack.length-1] != undefined){ // url(dir/center/image.png)
126         return ret("string", "string");
127       }else if( stream.current().match(/\-\d|\-.\d/) ){ // match e.g.: -5px -0.4 etc... only colorize the minus sign
128         //commment out these 2 comment if you want the minus sign to be parsed as null -500px
129         //stream.backUp(stream.current().length-1);
130         //return ret(null, ch); //console.log( stream.current() );
131         return ret("number", "unit");
132       }else if( /\/|[\s\)]/.test(stream.peek() || stream.eol() || (stream.eatSpace() && stream.peek() == "/")) && stream.current().indexOf(".") !== -1){
133         if(stream.current().substring(stream.current().length-1,stream.current().length) == "{"){
134           stream.backUp(1);
135           return ret("tag", "tag");
136         }//end if
137         stream.eatSpace();
138         if( /[{<>.a-zA-Z\/]/.test(stream.peek())  || stream.eol() )return ret("tag", "tag"); // e.g. button.icon-plus
139         return ret("string", "string"); // let url(/images/logo.png) without quotes return as string
140       }else if( stream.eol() || stream.peek() == "[" || stream.peek() == "#" || type == "tag" ){
141         if(stream.current().substring(stream.current().length-1,stream.current().length) == "{")stream.backUp(1);
142         return ret("tag", "tag");
143       }else if(type == "compare" || type == "a" || type == "("){
144         return ret("string", "string");
145       }else if(type == "|" || stream.current() == "-" || type == "["){
146         return ret(null, ch);
147       }else if(stream.peek() == ":") {
148         stream.next();
149         var t_v = stream.peek() == ":" ? true : false;
150         if(!t_v){
151           var old_pos = stream.pos;
152           var sc = stream.current().length;
153           stream.eatWhile(/[a-z\\\-]/);
154           var new_pos = stream.pos;
155           if(stream.current().substring(sc-1).match(selectors) != null){
156             stream.backUp(new_pos-(old_pos-1));
157             return ret("tag", "tag");
158           } else stream.backUp(new_pos-(old_pos-1));
159         }else{
160           stream.backUp(1);
161         }
162         if(t_v)return ret("tag", "tag"); else return ret("variable", "variable");
163       }else{
164         return ret("variable", "variable");
165       }
166     }
167   }
168
169   function tokenSComment(stream, state) { // SComment = Slash comment
170     stream.skipToEnd();
171     state.tokenize = tokenBase;
172     return ret("comment", "comment");
173   }
174
175   function tokenCComment(stream, state) {
176     var maybeEnd = false, ch;
177     while ((ch = stream.next()) != null) {
178       if (maybeEnd && ch == "/") {
179         state.tokenize = tokenBase;
180         break;
181       }
182       maybeEnd = (ch == "*");
183     }
184     return ret("comment", "comment");
185   }
186
187   function tokenSGMLComment(stream, state) {
188     var dashes = 0, ch;
189     while ((ch = stream.next()) != null) {
190       if (dashes >= 2 && ch == ">") {
191         state.tokenize = tokenBase;
192         break;
193       }
194       dashes = (ch == "-") ? dashes + 1 : 0;
195     }
196     return ret("comment", "comment");
197   }
198
199   function tokenString(quote) {
200     return function(stream, state) {
201       var escaped = false, ch;
202       while ((ch = stream.next()) != null) {
203         if (ch == quote && !escaped)
204           break;
205         escaped = !escaped && ch == "\\";
206       }
207       if (!escaped) state.tokenize = tokenBase;
208       return ret("string", "string");
209     };
210   }
211
212   return {
213     startState: function(base) {
214       return {tokenize: tokenBase,
215               baseIndent: base || 0,
216               stack: []};
217     },
218
219     token: function(stream, state) {
220       if (stream.eatSpace()) return null;
221       var style = state.tokenize(stream, state);
222
223       var context = state.stack[state.stack.length-1];
224       if (type == "hash" && context == "rule") style = "atom";
225       else if (style == "variable") {
226         if (context == "rule") style = null; //"tag"
227         else if (!context || context == "@media{") {
228           style = stream.current() == "when"  ? "variable" :
229           /[\s,|\s\)|\s]/.test(stream.peek()) ? "tag"      : type;
230         }
231       }
232
233       if (context == "rule" && /^[\{\};]$/.test(type))
234         state.stack.pop();
235       if (type == "{") {
236         if (context == "@media") state.stack[state.stack.length-1] = "@media{";
237         else state.stack.push("{");
238       }
239       else if (type == "}") state.stack.pop();
240       else if (type == "@media") state.stack.push("@media");
241       else if (context == "{" && type != "comment") state.stack.push("rule");
242       return style;
243     },
244
245     indent: function(state, textAfter) {
246       var n = state.stack.length;
247       if (/^\}/.test(textAfter))
248         n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1;
249       return state.baseIndent + n * indentUnit;
250     },
251
252     electricChars: "}"
253   };
254 });
255
256 CodeMirror.defineMIME("text/x-less", "less");
257 if (!CodeMirror.mimeModes.hasOwnProperty("text/css"))
258   CodeMirror.defineMIME("text/css", "less");