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
38using namespace Lexilla;
39
40
41static 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
50inline 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
63inline 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
78static 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 _CommentModes { eCommentBlock = 0, eCommentLine = 1} CommentMode;
119 CommentMode comment_mode = eCommentBlock;
120 bool hasSingleLineComments = 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
508static void FoldCSSDoc(Sci_PositionU startPos, Sci_Position length, int, WordList *[], Accessor &styler) {
509 bool foldComment = 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 inComment = (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
558static 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
570LexerModule lmCss(SCLEX_CSS, ColouriseCssDoc, "css", FoldCSSDoc, cssWordListDesc);
571