Implement CSS `display: flow-root` (modern clearfix)
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / External / CodeMirror / sass.js
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: http://codemirror.net/LICENSE
3
4 (function(mod) {
5   if (typeof exports == "object" && typeof module == "object") // CommonJS
6     mod(require("../../lib/codemirror"));
7   else if (typeof define == "function" && define.amd) // AMD
8     define(["../../lib/codemirror"], mod);
9   else // Plain browser env
10     mod(CodeMirror);
11 })(function(CodeMirror) {
12 "use strict";
13
14 CodeMirror.defineMode("sass", function(config) {
15   function tokenRegexp(words) {
16     return new RegExp("^" + words.join("|"));
17   }
18
19   var keywords = ["true", "false", "null", "auto"];
20   var keywordsRegexp = new RegExp("^" + keywords.join("|"));
21
22   var operators = ["\\(", "\\)", "=", ">", "<", "==", ">=", "<=", "\\+", "-",
23                    "\\!=", "/", "\\*", "%", "and", "or", "not", ";","\\{","\\}",":"];
24   var opRegexp = tokenRegexp(operators);
25
26   var pseudoElementsRegexp = /^::?[a-zA-Z_][\w\-]*/;
27
28   function urlTokens(stream, state) {
29     var ch = stream.peek();
30
31     if (ch === ")") {
32       stream.next();
33       state.tokenizer = tokenBase;
34       return "operator";
35     } else if (ch === "(") {
36       stream.next();
37       stream.eatSpace();
38
39       return "operator";
40     } else if (ch === "'" || ch === '"') {
41       state.tokenizer = buildStringTokenizer(stream.next());
42       return "string";
43     } else {
44       state.tokenizer = buildStringTokenizer(")", false);
45       return "string";
46     }
47   }
48   function comment(indentation, multiLine) {
49     return function(stream, state) {
50       if (stream.sol() && stream.indentation() <= indentation) {
51         state.tokenizer = tokenBase;
52         return tokenBase(stream, state);
53       }
54
55       if (multiLine && stream.skipTo("*/")) {
56         stream.next();
57         stream.next();
58         state.tokenizer = tokenBase;
59       } else {
60         stream.skipToEnd();
61       }
62
63       return "comment";
64     };
65   }
66
67   function buildStringTokenizer(quote, greedy) {
68     if (greedy == null) { greedy = true; }
69
70     function stringTokenizer(stream, state) {
71       var nextChar = stream.next();
72       var peekChar = stream.peek();
73       var previousChar = stream.string.charAt(stream.pos-2);
74
75       var endingString = ((nextChar !== "\\" && peekChar === quote) || (nextChar === quote && previousChar !== "\\"));
76
77       if (endingString) {
78         if (nextChar !== quote && greedy) { stream.next(); }
79         state.tokenizer = tokenBase;
80         return "string";
81       } else if (nextChar === "#" && peekChar === "{") {
82         state.tokenizer = buildInterpolationTokenizer(stringTokenizer);
83         stream.next();
84         return "operator";
85       } else {
86         return "string";
87       }
88     }
89
90     return stringTokenizer;
91   }
92
93   function buildInterpolationTokenizer(currentTokenizer) {
94     return function(stream, state) {
95       if (stream.peek() === "}") {
96         stream.next();
97         state.tokenizer = currentTokenizer;
98         return "operator";
99       } else {
100         return tokenBase(stream, state);
101       }
102     };
103   }
104
105   function indent(state) {
106     if (state.indentCount == 0) {
107       state.indentCount++;
108       var lastScopeOffset = state.scopes[0].offset;
109       var currentOffset = lastScopeOffset + config.indentUnit;
110       state.scopes.unshift({ offset:currentOffset });
111     }
112   }
113
114   function dedent(state) {
115     if (state.scopes.length == 1) return;
116
117     state.scopes.shift();
118   }
119
120   function tokenBase(stream, state) {
121     var ch = stream.peek();
122
123     // Comment
124     if (stream.match("/*")) {
125       state.tokenizer = comment(stream.indentation(), true);
126       return state.tokenizer(stream, state);
127     }
128     if (stream.match("//")) {
129       state.tokenizer = comment(stream.indentation(), false);
130       return state.tokenizer(stream, state);
131     }
132
133     // Interpolation
134     if (stream.match("#{")) {
135       state.tokenizer = buildInterpolationTokenizer(tokenBase);
136       return "operator";
137     }
138
139     // Strings
140     if (ch === '"' || ch === "'") {
141       stream.next();
142       state.tokenizer = buildStringTokenizer(ch);
143       return "string";
144     }
145
146     if(!state.cursorHalf){// state.cursorHalf === 0
147     // first half i.e. before : for key-value pairs
148     // including selectors
149
150       if (ch === ".") {
151         stream.next();
152         if (stream.match(/^[\w-]+/)) {
153           indent(state);
154           return "atom";
155         } else if (stream.peek() === "#") {
156           indent(state);
157           return "atom";
158         }
159       }
160
161       if (ch === "#") {
162         stream.next();
163         // ID selectors
164         if (stream.match(/^[\w-]+/)) {
165           indent(state);
166           return "atom";
167         }
168         if (stream.peek() === "#") {
169           indent(state);
170           return "atom";
171         }
172       }
173
174       // Variables
175       if (ch === "$") {
176         stream.next();
177         stream.eatWhile(/[\w-]/);
178         return "variable-2";
179       }
180
181       // Numbers
182       if (stream.match(/^-?[0-9\.]+/))
183         return "number";
184
185       // Units
186       if (stream.match(/^(px|em|in)\b/))
187         return "unit";
188
189       if (stream.match(keywordsRegexp))
190         return "keyword";
191
192       if (stream.match(/^url/) && stream.peek() === "(") {
193         state.tokenizer = urlTokens;
194         return "atom";
195       }
196
197       if (ch === "=") {
198         // Match shortcut mixin definition
199         if (stream.match(/^=[\w-]+/)) {
200           indent(state);
201           return "meta";
202         }
203       }
204
205       if (ch === "+") {
206         // Match shortcut mixin definition
207         if (stream.match(/^\+[\w-]+/)){
208           return "variable-3";
209         }
210       }
211
212       if(ch === "@"){
213         if(stream.match(/@extend/)){
214           if(!stream.match(/\s*[\w]/))
215             dedent(state);
216         }
217       }
218
219
220       // Indent Directives
221       if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) {
222         indent(state);
223         return "meta";
224       }
225
226       // Other Directives
227       if (ch === "@") {
228         stream.next();
229         stream.eatWhile(/[\w-]/);
230         return "meta";
231       }
232
233       if (stream.eatWhile(/[\w-]/)){
234         if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){
235           return "property";
236         }
237         else if(stream.match(/ *:/,false)){
238           indent(state);
239           state.cursorHalf = 1;
240           return "atom";
241         }
242         else if(stream.match(/ *,/,false)){
243           return "atom";
244         }
245         else{
246           indent(state);
247           return "atom";
248         }
249       }
250
251       if(ch === ":"){
252         if (stream.match(pseudoElementsRegexp)){ // could be a pseudo-element
253           return "keyword";
254         }
255         stream.next();
256         state.cursorHalf=1;
257         return "operator";
258       }
259
260     } // cursorHalf===0 ends here
261     else{
262
263       if (ch === "#") {
264         stream.next();
265         // Hex numbers
266         if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){
267           if(!stream.peek()){
268             state.cursorHalf = 0;
269           }
270           return "number";
271         }
272       }
273
274       // Numbers
275       if (stream.match(/^-?[0-9\.]+/)){
276         if(!stream.peek()){
277           state.cursorHalf = 0;
278         }
279         return "number";
280       }
281
282       // Units
283       if (stream.match(/^(px|em|in)\b/)){
284         if(!stream.peek()){
285           state.cursorHalf = 0;
286         }
287         return "unit";
288       }
289
290       if (stream.match(keywordsRegexp)){
291         if(!stream.peek()){
292           state.cursorHalf = 0;
293         }
294         return "keyword";
295       }
296
297       if (stream.match(/^url/) && stream.peek() === "(") {
298         state.tokenizer = urlTokens;
299         if(!stream.peek()){
300           state.cursorHalf = 0;
301         }
302         return "atom";
303       }
304
305       // Variables
306       if (ch === "$") {
307         stream.next();
308         stream.eatWhile(/[\w-]/);
309         if(!stream.peek()){
310           state.cursorHalf = 0;
311         }
312         return "variable-3";
313       }
314
315       // bang character for !important, !default, etc.
316       if (ch === "!") {
317         stream.next();
318         if(!stream.peek()){
319           state.cursorHalf = 0;
320         }
321         return stream.match(/^[\w]+/) ? "keyword": "operator";
322       }
323
324       if (stream.match(opRegexp)){
325         if(!stream.peek()){
326           state.cursorHalf = 0;
327         }
328         return "operator";
329       }
330
331       // attributes
332       if (stream.eatWhile(/[\w-]/)) {
333         if(!stream.peek()){
334           state.cursorHalf = 0;
335         }
336         return "attribute";
337       }
338
339       //stream.eatSpace();
340       if(!stream.peek()){
341         state.cursorHalf = 0;
342         return null;
343       }
344
345     } // else ends here
346
347     if (stream.match(opRegexp))
348       return "operator";
349
350     // If we haven't returned by now, we move 1 character
351     // and return an error
352     stream.next();
353     return null;
354   }
355
356   function tokenLexer(stream, state) {
357     if (stream.sol()) state.indentCount = 0;
358     var style = state.tokenizer(stream, state);
359     var current = stream.current();
360
361     if (current === "@return" || current === "}"){
362       dedent(state);
363     }
364
365     if (style !== null) {
366       var startOfToken = stream.pos - current.length;
367
368       var withCurrentIndent = startOfToken + (config.indentUnit * state.indentCount);
369
370       var newScopes = [];
371
372       for (var i = 0; i < state.scopes.length; i++) {
373         var scope = state.scopes[i];
374
375         if (scope.offset <= withCurrentIndent)
376           newScopes.push(scope);
377       }
378
379       state.scopes = newScopes;
380     }
381
382
383     return style;
384   }
385
386   return {
387     startState: function() {
388       return {
389         tokenizer: tokenBase,
390         scopes: [{offset: 0, type: "sass"}],
391         indentCount: 0,
392         cursorHalf: 0,  // cursor half tells us if cursor lies after (1)
393                         // or before (0) colon (well... more or less)
394         definedVars: [],
395         definedMixins: []
396       };
397     },
398     token: function(stream, state) {
399       var style = tokenLexer(stream, state);
400
401       state.lastToken = { style: style, content: stream.current() };
402
403       return style;
404     },
405
406     indent: function(state) {
407       return state.scopes[0].offset;
408     }
409   };
410 });
411
412 CodeMirror.defineMIME("text/x-sass", "sass");
413
414 });