1 | // Scintilla source code edit control |
2 | // Encoding: UTF-8 |
3 | /** @file LexCSS.cxx |
4 | ** Lexer for Cascading Style Sheets |
5 | ** Written by Jakub Vrána |
6 | ** Improved by Philippe Lhoste (CSS2) |
7 | ** Improved by Ross McKay (SCSS mode; see http://sass-lang.com/ ) |
8 | **/ |
9 | // Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org> |
10 | // The License.txt file describes the conditions under which this software may be distributed. |
11 | |
12 | // TODO: handle SCSS nested properties like font: { weight: bold; size: 1em; } |
13 | // TODO: handle SCSS interpolation: #{} |
14 | // TODO: add features for Less if somebody feels like contributing; http://lesscss.org/ |
15 | // TODO: refactor this monster so that the next poor slob can read it! |
16 | |
17 | #include <stdlib.h> |
18 | #include <string.h> |
19 | #include <stdio.h> |
20 | #include <stdarg.h> |
21 | #include <assert.h> |
22 | #include <ctype.h> |
23 | |
24 | #include <string> |
25 | #include <string_view> |
26 | |
27 | #include "ILexer.h" |
28 | #include "Scintilla.h" |
29 | #include "SciLexer.h" |
30 | |
31 | #include "WordList.h" |
32 | #include "LexAccessor.h" |
33 | #include "Accessor.h" |
34 | #include "StyleContext.h" |
35 | #include "CharacterSet.h" |
36 | #include "LexerModule.h" |
37 | |
38 | using namespace Lexilla; |
39 | |
40 | |
41 | static inline bool IsAWordChar(const unsigned int ch) { |
42 | /* FIXME: |
43 | * The CSS spec allows "ISO 10646 characters U+00A1 and higher" to be treated as word chars. |
44 | * Unfortunately, we are only getting string bytes here, and not full unicode characters. We cannot guarantee |
45 | * that our byte is between U+0080 - U+00A0 (to return false), so we have to allow all characters U+0080 and higher |
46 | */ |
47 | return ch >= 0x80 || isalnum(ch) || ch == '-' || ch == '_'; |
48 | } |
49 | |
50 | inline bool IsCssOperator(const int ch) { |
51 | if (!((ch < 0x80) && isalnum(ch)) && |
52 | (ch == '{' || ch == '}' || ch == ':' || ch == ',' || ch == ';' || |
53 | ch == '.' || ch == '#' || ch == '!' || ch == '@' || |
54 | /* CSS2 */ |
55 | ch == '*' || ch == '>' || ch == '+' || ch == '=' || ch == '~' || ch == '|' || |
56 | ch == '[' || ch == ']' || ch == '(' || ch == ')')) { |
57 | return true; |
58 | } |
59 | return false; |
60 | } |
61 | |
62 | // look behind (from start of document to our start position) to determine current nesting level |
63 | inline int NestingLevelLookBehind(Sci_PositionU startPos, Accessor &styler) { |
64 | int ch; |
65 | int nestingLevel = 0; |
66 | |
67 | for (Sci_PositionU i = 0; i < startPos; i++) { |
68 | ch = styler.SafeGetCharAt(i); |
69 | if (ch == '{') |
70 | nestingLevel++; |
71 | else if (ch == '}') |
72 | nestingLevel--; |
73 | } |
74 | |
75 | return nestingLevel; |
76 | } |
77 | |
78 | static void ColouriseCssDoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *keywordlists[], Accessor &styler) { |
79 | WordList &css1Props = *keywordlists[0]; |
80 | WordList &pseudoClasses = *keywordlists[1]; |
81 | WordList &css2Props = *keywordlists[2]; |
82 | WordList &css3Props = *keywordlists[3]; |
83 | WordList &pseudoElements = *keywordlists[4]; |
84 | WordList &exProps = *keywordlists[5]; |
85 | WordList &exPseudoClasses = *keywordlists[6]; |
86 | WordList &exPseudoElements = *keywordlists[7]; |
87 | |
88 | StyleContext sc(startPos, length, initStyle, styler); |
89 | |
90 | int lastState = -1; // before operator |
91 | int lastStateC = -1; // before comment |
92 | int lastStateS = -1; // before single-quoted/double-quoted string |
93 | int lastStateVar = -1; // before variable (SCSS) |
94 | int lastStateVal = -1; // before value (SCSS) |
95 | int op = ' '; // last operator |
96 | int opPrev = ' '; // last operator |
97 | bool insideParentheses = false; // true if currently in a CSS url() or similar construct |
98 | |
99 | // property lexer.css.scss.language |
100 | // Set to 1 for Sassy CSS (.scss) |
101 | bool isScssDocument = styler.GetPropertyInt("lexer.css.scss.language" ) != 0; |
102 | |
103 | // property lexer.css.less.language |
104 | // Set to 1 for Less CSS (.less) |
105 | bool isLessDocument = styler.GetPropertyInt("lexer.css.less.language" ) != 0; |
106 | |
107 | // property lexer.css.hss.language |
108 | // Set to 1 for HSS (.hss) |
109 | bool isHssDocument = styler.GetPropertyInt("lexer.css.hss.language" ) != 0; |
110 | |
111 | // SCSS/LESS/HSS have the concept of variable |
112 | bool hasVariables = isScssDocument || isLessDocument || isHssDocument; |
113 | char varPrefix = 0; |
114 | if (hasVariables) |
115 | varPrefix = isLessDocument ? '@' : '$'; |
116 | |
117 | // SCSS/LESS/HSS support single-line comments |
118 | typedef enum { = 0, = 1} ; |
119 | CommentMode = eCommentBlock; |
120 | bool = isScssDocument || isLessDocument || isHssDocument; |
121 | |
122 | // must keep track of nesting level in document types that support it (SCSS/LESS/HSS) |
123 | bool hasNesting = false; |
124 | int nestingLevel = 0; |
125 | if (isScssDocument || isLessDocument || isHssDocument) { |
126 | hasNesting = true; |
127 | nestingLevel = NestingLevelLookBehind(startPos, styler); |
128 | } |
129 | |
130 | // "the loop" |
131 | for (; sc.More(); sc.Forward()) { |
132 | if (sc.state == SCE_CSS_COMMENT && ((comment_mode == eCommentBlock && sc.Match('*', '/')) || (comment_mode == eCommentLine && sc.atLineEnd))) { |
133 | if (lastStateC == -1) { |
134 | // backtrack to get last state: |
135 | // comments are like whitespace, so we must return to the previous state |
136 | Sci_PositionU i = startPos; |
137 | for (; i > 0; i--) { |
138 | if ((lastStateC = styler.StyleAt(i-1)) != SCE_CSS_COMMENT) { |
139 | if (lastStateC == SCE_CSS_OPERATOR) { |
140 | op = styler.SafeGetCharAt(i-1); |
141 | opPrev = styler.SafeGetCharAt(i-2); |
142 | while (--i) { |
143 | lastState = styler.StyleAt(i-1); |
144 | if (lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT) |
145 | break; |
146 | } |
147 | if (i == 0) |
148 | lastState = SCE_CSS_DEFAULT; |
149 | } |
150 | break; |
151 | } |
152 | } |
153 | if (i == 0) |
154 | lastStateC = SCE_CSS_DEFAULT; |
155 | } |
156 | if (comment_mode == eCommentBlock) { |
157 | sc.Forward(); |
158 | sc.ForwardSetState(lastStateC); |
159 | } else /* eCommentLine */ { |
160 | sc.SetState(lastStateC); |
161 | } |
162 | } |
163 | |
164 | if (sc.state == SCE_CSS_COMMENT) |
165 | continue; |
166 | |
167 | if (sc.state == SCE_CSS_DOUBLESTRING || sc.state == SCE_CSS_SINGLESTRING) { |
168 | if (sc.ch != (sc.state == SCE_CSS_DOUBLESTRING ? '\"' : '\'')) |
169 | continue; |
170 | Sci_PositionU i = sc.currentPos; |
171 | while (i && styler[i-1] == '\\') |
172 | i--; |
173 | if ((sc.currentPos - i) % 2 == 1) |
174 | continue; |
175 | sc.ForwardSetState(lastStateS); |
176 | } |
177 | |
178 | if (sc.state == SCE_CSS_OPERATOR) { |
179 | if (op == ' ') { |
180 | Sci_PositionU i = startPos; |
181 | op = styler.SafeGetCharAt(i-1); |
182 | opPrev = styler.SafeGetCharAt(i-2); |
183 | while (--i) { |
184 | lastState = styler.StyleAt(i-1); |
185 | if (lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT) |
186 | break; |
187 | } |
188 | } |
189 | switch (op) { |
190 | case '@': |
191 | if (lastState == SCE_CSS_DEFAULT || hasNesting) |
192 | sc.SetState(SCE_CSS_DIRECTIVE); |
193 | break; |
194 | case '>': |
195 | case '+': |
196 | if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID || |
197 | lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS) |
198 | sc.SetState(SCE_CSS_DEFAULT); |
199 | break; |
200 | case '[': |
201 | if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID || |
202 | lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS) |
203 | sc.SetState(SCE_CSS_ATTRIBUTE); |
204 | break; |
205 | case ']': |
206 | if (lastState == SCE_CSS_ATTRIBUTE) |
207 | sc.SetState(SCE_CSS_TAG); |
208 | break; |
209 | case '{': |
210 | nestingLevel++; |
211 | switch (lastState) { |
212 | case SCE_CSS_GROUP_RULE: |
213 | sc.SetState(SCE_CSS_DEFAULT); |
214 | break; |
215 | case SCE_CSS_TAG: |
216 | case SCE_CSS_DIRECTIVE: |
217 | sc.SetState(SCE_CSS_IDENTIFIER); |
218 | break; |
219 | } |
220 | break; |
221 | case '}': |
222 | if (--nestingLevel < 0) |
223 | nestingLevel = 0; |
224 | switch (lastState) { |
225 | case SCE_CSS_DEFAULT: |
226 | case SCE_CSS_VALUE: |
227 | case SCE_CSS_IMPORTANT: |
228 | case SCE_CSS_IDENTIFIER: |
229 | case SCE_CSS_IDENTIFIER2: |
230 | case SCE_CSS_IDENTIFIER3: |
231 | if (hasNesting) |
232 | sc.SetState(nestingLevel > 0 ? SCE_CSS_IDENTIFIER : SCE_CSS_DEFAULT); |
233 | else |
234 | sc.SetState(SCE_CSS_DEFAULT); |
235 | break; |
236 | } |
237 | break; |
238 | case '(': |
239 | if (lastState == SCE_CSS_PSEUDOCLASS) |
240 | sc.SetState(SCE_CSS_TAG); |
241 | else if (lastState == SCE_CSS_EXTENDED_PSEUDOCLASS) |
242 | sc.SetState(SCE_CSS_EXTENDED_PSEUDOCLASS); |
243 | break; |
244 | case ')': |
245 | if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID || |
246 | lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS || |
247 | lastState == SCE_CSS_PSEUDOELEMENT || lastState == SCE_CSS_EXTENDED_PSEUDOELEMENT) |
248 | sc.SetState(SCE_CSS_TAG); |
249 | break; |
250 | case ':': |
251 | switch (lastState) { |
252 | case SCE_CSS_TAG: |
253 | case SCE_CSS_DEFAULT: |
254 | case SCE_CSS_CLASS: |
255 | case SCE_CSS_ID: |
256 | case SCE_CSS_PSEUDOCLASS: |
257 | case SCE_CSS_EXTENDED_PSEUDOCLASS: |
258 | case SCE_CSS_UNKNOWN_PSEUDOCLASS: |
259 | case SCE_CSS_PSEUDOELEMENT: |
260 | case SCE_CSS_EXTENDED_PSEUDOELEMENT: |
261 | sc.SetState(SCE_CSS_PSEUDOCLASS); |
262 | break; |
263 | case SCE_CSS_IDENTIFIER: |
264 | case SCE_CSS_IDENTIFIER2: |
265 | case SCE_CSS_IDENTIFIER3: |
266 | case SCE_CSS_EXTENDED_IDENTIFIER: |
267 | case SCE_CSS_UNKNOWN_IDENTIFIER: |
268 | case SCE_CSS_VARIABLE: |
269 | sc.SetState(SCE_CSS_VALUE); |
270 | lastStateVal = lastState; |
271 | break; |
272 | } |
273 | break; |
274 | case '.': |
275 | if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID || |
276 | lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS) |
277 | sc.SetState(SCE_CSS_CLASS); |
278 | break; |
279 | case '#': |
280 | if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID || |
281 | lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS) |
282 | sc.SetState(SCE_CSS_ID); |
283 | break; |
284 | case ',': |
285 | case '|': |
286 | case '~': |
287 | if (lastState == SCE_CSS_TAG) |
288 | sc.SetState(SCE_CSS_DEFAULT); |
289 | break; |
290 | case ';': |
291 | switch (lastState) { |
292 | case SCE_CSS_DIRECTIVE: |
293 | if (hasNesting) { |
294 | sc.SetState(nestingLevel > 0 ? SCE_CSS_IDENTIFIER : SCE_CSS_DEFAULT); |
295 | } else { |
296 | sc.SetState(SCE_CSS_DEFAULT); |
297 | } |
298 | break; |
299 | case SCE_CSS_VALUE: |
300 | case SCE_CSS_IMPORTANT: |
301 | // data URLs can have semicolons; simplistically check for wrapping parentheses and move along |
302 | if (insideParentheses) { |
303 | sc.SetState(lastState); |
304 | } else { |
305 | if (lastStateVal == SCE_CSS_VARIABLE) { |
306 | sc.SetState(SCE_CSS_DEFAULT); |
307 | } else { |
308 | sc.SetState(SCE_CSS_IDENTIFIER); |
309 | } |
310 | } |
311 | break; |
312 | case SCE_CSS_VARIABLE: |
313 | if (lastStateVar == SCE_CSS_VALUE) { |
314 | // data URLs can have semicolons; simplistically check for wrapping parentheses and move along |
315 | if (insideParentheses) { |
316 | sc.SetState(SCE_CSS_VALUE); |
317 | } else { |
318 | sc.SetState(SCE_CSS_IDENTIFIER); |
319 | } |
320 | } else { |
321 | sc.SetState(SCE_CSS_DEFAULT); |
322 | } |
323 | break; |
324 | } |
325 | break; |
326 | case '!': |
327 | if (lastState == SCE_CSS_VALUE) |
328 | sc.SetState(SCE_CSS_IMPORTANT); |
329 | break; |
330 | } |
331 | } |
332 | |
333 | if (sc.ch == '*' && sc.state == SCE_CSS_DEFAULT) { |
334 | sc.SetState(SCE_CSS_TAG); |
335 | continue; |
336 | } |
337 | |
338 | // check for inside parentheses (whether part of an "operator" or not) |
339 | if (sc.ch == '(') |
340 | insideParentheses = true; |
341 | else if (sc.ch == ')') |
342 | insideParentheses = false; |
343 | |
344 | // SCSS special modes |
345 | if (hasVariables) { |
346 | // variable name |
347 | if (sc.ch == varPrefix) { |
348 | switch (sc.state) { |
349 | case SCE_CSS_DEFAULT: |
350 | if (isLessDocument) // give priority to pseudo elements |
351 | break; |
352 | // Falls through. |
353 | case SCE_CSS_VALUE: |
354 | lastStateVar = sc.state; |
355 | sc.SetState(SCE_CSS_VARIABLE); |
356 | continue; |
357 | } |
358 | } |
359 | if (sc.state == SCE_CSS_VARIABLE) { |
360 | if (IsAWordChar(sc.ch)) { |
361 | // still looking at the variable name |
362 | continue; |
363 | } |
364 | if (lastStateVar == SCE_CSS_VALUE) { |
365 | // not looking at the variable name any more, and it was part of a value |
366 | sc.SetState(SCE_CSS_VALUE); |
367 | } |
368 | } |
369 | |
370 | // nested rule parent selector |
371 | if (sc.ch == '&') { |
372 | switch (sc.state) { |
373 | case SCE_CSS_DEFAULT: |
374 | case SCE_CSS_IDENTIFIER: |
375 | sc.SetState(SCE_CSS_TAG); |
376 | continue; |
377 | } |
378 | } |
379 | } |
380 | |
381 | // nesting rules that apply to SCSS and Less |
382 | if (hasNesting) { |
383 | // check for nested rule selector |
384 | if (sc.state == SCE_CSS_IDENTIFIER && (IsAWordChar(sc.ch) || sc.ch == ':' || sc.ch == '.' || sc.ch == '#')) { |
385 | // look ahead to see whether { comes before next ; and } |
386 | Sci_PositionU endPos = startPos + length; |
387 | int ch; |
388 | |
389 | for (Sci_PositionU i = sc.currentPos; i < endPos; i++) { |
390 | ch = styler.SafeGetCharAt(i); |
391 | if (ch == ';' || ch == '}') |
392 | break; |
393 | if (ch == '{') { |
394 | sc.SetState(SCE_CSS_DEFAULT); |
395 | continue; |
396 | } |
397 | } |
398 | } |
399 | |
400 | } |
401 | |
402 | if (IsAWordChar(sc.ch)) { |
403 | if (sc.state == SCE_CSS_DEFAULT) |
404 | sc.SetState(SCE_CSS_TAG); |
405 | continue; |
406 | } |
407 | |
408 | if (IsAWordChar(sc.chPrev) && ( |
409 | sc.state == SCE_CSS_IDENTIFIER || sc.state == SCE_CSS_IDENTIFIER2 || |
410 | sc.state == SCE_CSS_IDENTIFIER3 || sc.state == SCE_CSS_EXTENDED_IDENTIFIER || |
411 | sc.state == SCE_CSS_UNKNOWN_IDENTIFIER || |
412 | sc.state == SCE_CSS_PSEUDOCLASS || sc.state == SCE_CSS_PSEUDOELEMENT || |
413 | sc.state == SCE_CSS_EXTENDED_PSEUDOCLASS || sc.state == SCE_CSS_EXTENDED_PSEUDOELEMENT || |
414 | sc.state == SCE_CSS_UNKNOWN_PSEUDOCLASS || |
415 | sc.state == SCE_CSS_IMPORTANT || |
416 | sc.state == SCE_CSS_DIRECTIVE |
417 | )) { |
418 | char s[100]; |
419 | sc.GetCurrentLowered(s, sizeof(s)); |
420 | char *s2 = s; |
421 | while (*s2 && !IsAWordChar(*s2)) |
422 | s2++; |
423 | switch (sc.state) { |
424 | case SCE_CSS_IDENTIFIER: |
425 | case SCE_CSS_IDENTIFIER2: |
426 | case SCE_CSS_IDENTIFIER3: |
427 | case SCE_CSS_EXTENDED_IDENTIFIER: |
428 | case SCE_CSS_UNKNOWN_IDENTIFIER: |
429 | if (css1Props.InList(s2)) |
430 | sc.ChangeState(SCE_CSS_IDENTIFIER); |
431 | else if (css2Props.InList(s2)) |
432 | sc.ChangeState(SCE_CSS_IDENTIFIER2); |
433 | else if (css3Props.InList(s2)) |
434 | sc.ChangeState(SCE_CSS_IDENTIFIER3); |
435 | else if (exProps.InList(s2)) |
436 | sc.ChangeState(SCE_CSS_EXTENDED_IDENTIFIER); |
437 | else |
438 | sc.ChangeState(SCE_CSS_UNKNOWN_IDENTIFIER); |
439 | break; |
440 | case SCE_CSS_PSEUDOCLASS: |
441 | case SCE_CSS_PSEUDOELEMENT: |
442 | case SCE_CSS_EXTENDED_PSEUDOCLASS: |
443 | case SCE_CSS_EXTENDED_PSEUDOELEMENT: |
444 | case SCE_CSS_UNKNOWN_PSEUDOCLASS: |
445 | if (op == ':' && opPrev != ':' && pseudoClasses.InList(s2)) |
446 | sc.ChangeState(SCE_CSS_PSEUDOCLASS); |
447 | else if (opPrev == ':' && pseudoElements.InList(s2)) |
448 | sc.ChangeState(SCE_CSS_PSEUDOELEMENT); |
449 | else if ((op == ':' || (op == '(' && lastState == SCE_CSS_EXTENDED_PSEUDOCLASS)) && opPrev != ':' && exPseudoClasses.InList(s2)) |
450 | sc.ChangeState(SCE_CSS_EXTENDED_PSEUDOCLASS); |
451 | else if (opPrev == ':' && exPseudoElements.InList(s2)) |
452 | sc.ChangeState(SCE_CSS_EXTENDED_PSEUDOELEMENT); |
453 | else |
454 | sc.ChangeState(SCE_CSS_UNKNOWN_PSEUDOCLASS); |
455 | break; |
456 | case SCE_CSS_IMPORTANT: |
457 | if (strcmp(s2, "important" ) != 0) |
458 | sc.ChangeState(SCE_CSS_VALUE); |
459 | break; |
460 | case SCE_CSS_DIRECTIVE: |
461 | if (op == '@' && (strcmp(s2, "media" ) == 0 || strcmp(s2, "supports" ) == 0 || strcmp(s2, "document" ) == 0 || strcmp(s2, "-moz-document" ) == 0)) |
462 | sc.ChangeState(SCE_CSS_GROUP_RULE); |
463 | break; |
464 | } |
465 | } |
466 | |
467 | if (sc.ch != '.' && sc.ch != ':' && sc.ch != '#' && ( |
468 | sc.state == SCE_CSS_CLASS || sc.state == SCE_CSS_ID || |
469 | (sc.ch != '(' && sc.ch != ')' && ( /* This line of the condition makes it possible to extend pseudo-classes with parentheses */ |
470 | sc.state == SCE_CSS_PSEUDOCLASS || sc.state == SCE_CSS_PSEUDOELEMENT || |
471 | sc.state == SCE_CSS_EXTENDED_PSEUDOCLASS || sc.state == SCE_CSS_EXTENDED_PSEUDOELEMENT || |
472 | sc.state == SCE_CSS_UNKNOWN_PSEUDOCLASS |
473 | )) |
474 | )) |
475 | sc.SetState(SCE_CSS_TAG); |
476 | |
477 | if (sc.Match('/', '*')) { |
478 | lastStateC = sc.state; |
479 | comment_mode = eCommentBlock; |
480 | sc.SetState(SCE_CSS_COMMENT); |
481 | sc.Forward(); |
482 | } else if (hasSingleLineComments && sc.Match('/', '/') && !insideParentheses) { |
483 | // note that we've had to treat ([...]// as the start of a URL not a comment, e.g. url(http://example.com), url(//example.com) |
484 | lastStateC = sc.state; |
485 | comment_mode = eCommentLine; |
486 | sc.SetState(SCE_CSS_COMMENT); |
487 | sc.Forward(); |
488 | } else if ((sc.state == SCE_CSS_VALUE || sc.state == SCE_CSS_ATTRIBUTE) |
489 | && (sc.ch == '\"' || sc.ch == '\'')) { |
490 | lastStateS = sc.state; |
491 | sc.SetState((sc.ch == '\"' ? SCE_CSS_DOUBLESTRING : SCE_CSS_SINGLESTRING)); |
492 | } else if (IsCssOperator(sc.ch) |
493 | && (sc.state != SCE_CSS_ATTRIBUTE || sc.ch == ']') |
494 | && (sc.state != SCE_CSS_VALUE || sc.ch == ';' || sc.ch == '}' || sc.ch == '!') |
495 | && ((sc.state != SCE_CSS_DIRECTIVE && sc.state != SCE_CSS_GROUP_RULE) || sc.ch == ';' || sc.ch == '{') |
496 | ) { |
497 | if (sc.state != SCE_CSS_OPERATOR) |
498 | lastState = sc.state; |
499 | sc.SetState(SCE_CSS_OPERATOR); |
500 | op = sc.ch; |
501 | opPrev = sc.chPrev; |
502 | } |
503 | } |
504 | |
505 | sc.Complete(); |
506 | } |
507 | |
508 | static void FoldCSSDoc(Sci_PositionU startPos, Sci_Position length, int, WordList *[], Accessor &styler) { |
509 | bool = styler.GetPropertyInt("fold.comment" ) != 0; |
510 | bool foldCompact = styler.GetPropertyInt("fold.compact" , 1) != 0; |
511 | Sci_PositionU endPos = startPos + length; |
512 | int visibleChars = 0; |
513 | Sci_Position lineCurrent = styler.GetLine(startPos); |
514 | int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK; |
515 | int levelCurrent = levelPrev; |
516 | char chNext = styler[startPos]; |
517 | bool = (styler.StyleAt(startPos-1) == SCE_CSS_COMMENT); |
518 | for (Sci_PositionU i = startPos; i < endPos; i++) { |
519 | char ch = chNext; |
520 | chNext = styler.SafeGetCharAt(i + 1); |
521 | int style = styler.StyleAt(i); |
522 | bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n'); |
523 | if (foldComment) { |
524 | if (!inComment && (style == SCE_CSS_COMMENT)) |
525 | levelCurrent++; |
526 | else if (inComment && (style != SCE_CSS_COMMENT)) |
527 | levelCurrent--; |
528 | inComment = (style == SCE_CSS_COMMENT); |
529 | } |
530 | if (style == SCE_CSS_OPERATOR) { |
531 | if (ch == '{') { |
532 | levelCurrent++; |
533 | } else if (ch == '}') { |
534 | levelCurrent--; |
535 | } |
536 | } |
537 | if (atEOL) { |
538 | int lev = levelPrev; |
539 | if (visibleChars == 0 && foldCompact) |
540 | lev |= SC_FOLDLEVELWHITEFLAG; |
541 | if ((levelCurrent > levelPrev) && (visibleChars > 0)) |
542 | lev |= SC_FOLDLEVELHEADERFLAG; |
543 | if (lev != styler.LevelAt(lineCurrent)) { |
544 | styler.SetLevel(lineCurrent, lev); |
545 | } |
546 | lineCurrent++; |
547 | levelPrev = levelCurrent; |
548 | visibleChars = 0; |
549 | } |
550 | if (!isspacechar(ch)) |
551 | visibleChars++; |
552 | } |
553 | // Fill in the real level of the next line, keeping the current flags as they will be filled in later |
554 | int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK; |
555 | styler.SetLevel(lineCurrent, levelPrev | flagsNext); |
556 | } |
557 | |
558 | static const char * const cssWordListDesc[] = { |
559 | "CSS1 Properties" , |
560 | "Pseudo-classes" , |
561 | "CSS2 Properties" , |
562 | "CSS3 Properties" , |
563 | "Pseudo-elements" , |
564 | "Browser-Specific CSS Properties" , |
565 | "Browser-Specific Pseudo-classes" , |
566 | "Browser-Specific Pseudo-elements" , |
567 | 0 |
568 | }; |
569 | |
570 | LexerModule lmCss(SCLEX_CSS, ColouriseCssDoc, "css" , FoldCSSDoc, cssWordListDesc); |
571 | |