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