1 // Scintilla source code edit control
3 ** Lexer for Cascading Style Sheets
4 ** Written by Jakub Vrána
5 ** Improved by Philippe Lhoste (CSS2)
7 // Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
8 // The License.txt file describes the conditions under which this software may be distributed.
18 #include "Scintilla.h"
22 #include "LexAccessor.h"
24 #include "StyleContext.h"
25 #include "CharacterSet.h"
26 #include "LexerModule.h"
29 using namespace Scintilla
;
33 static inline bool IsAWordChar(const unsigned int ch
) {
35 * The CSS spec allows "ISO 10646 characters U+00A1 and higher" to be treated as word chars.
36 * Unfortunately, we are only getting string bytes here, and not full unicode characters. We cannot guarantee
37 * that our byte is between U+0080 - U+00A0 (to return false), so we have to allow all characters U+0080 and higher
39 return ch
>= 0x80 || isalnum(ch
) || ch
== '-' || ch
== '_';
42 inline bool IsCssOperator(const int ch
) {
43 if (!((ch
< 0x80) && isalnum(ch
)) &&
44 (ch
== '{' || ch
== '}' || ch
== ':' || ch
== ',' || ch
== ';' ||
45 ch
== '.' || ch
== '#' || ch
== '!' || ch
== '@' ||
47 ch
== '*' || ch
== '>' || ch
== '+' || ch
== '=' || ch
== '~' || ch
== '|' ||
48 ch
== '[' || ch
== ']' || ch
== '(' || ch
== ')')) {
54 static void ColouriseCssDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[], Accessor
&styler
) {
55 WordList
&css1Props
= *keywordlists
[0];
56 WordList
&pseudoClasses
= *keywordlists
[1];
57 WordList
&css2Props
= *keywordlists
[2];
58 WordList
&css3Props
= *keywordlists
[3];
59 WordList
&pseudoElements
= *keywordlists
[4];
60 WordList
&exProps
= *keywordlists
[5];
61 WordList
&exPseudoClasses
= *keywordlists
[6];
62 WordList
&exPseudoElements
= *keywordlists
[7];
64 StyleContext
sc(startPos
, length
, initStyle
, styler
);
66 int lastState
= -1; // before operator
67 int lastStateC
= -1; // before comment
68 int lastStateS
= -1; // before single-quoted/double-quoted string
69 int op
= ' '; // last operator
70 int opPrev
= ' '; // last operator
72 for (; sc
.More(); sc
.Forward()) {
73 if (sc
.state
== SCE_CSS_COMMENT
&& sc
.Match('*', '/')) {
74 if (lastStateC
== -1) {
75 // backtrack to get last state:
76 // comments are like whitespace, so we must return to the previous state
77 unsigned int i
= startPos
;
79 if ((lastStateC
= styler
.StyleAt(i
-1)) != SCE_CSS_COMMENT
) {
80 if (lastStateC
== SCE_CSS_OPERATOR
) {
81 op
= styler
.SafeGetCharAt(i
-1);
82 opPrev
= styler
.SafeGetCharAt(i
-2);
84 lastState
= styler
.StyleAt(i
-1);
85 if (lastState
!= SCE_CSS_OPERATOR
&& lastState
!= SCE_CSS_COMMENT
)
89 lastState
= SCE_CSS_DEFAULT
;
95 lastStateC
= SCE_CSS_DEFAULT
;
98 sc
.ForwardSetState(lastStateC
);
101 if (sc
.state
== SCE_CSS_COMMENT
)
104 if (sc
.state
== SCE_CSS_DOUBLESTRING
|| sc
.state
== SCE_CSS_SINGLESTRING
) {
105 if (sc
.ch
!= (sc
.state
== SCE_CSS_DOUBLESTRING
? '\"' : '\''))
107 unsigned int i
= sc
.currentPos
;
108 while (i
&& styler
[i
-1] == '\\')
110 if ((sc
.currentPos
- i
) % 2 == 1)
112 sc
.ForwardSetState(lastStateS
);
115 if (sc
.state
== SCE_CSS_OPERATOR
) {
117 unsigned int i
= startPos
;
118 op
= styler
.SafeGetCharAt(i
-1);
119 opPrev
= styler
.SafeGetCharAt(i
-2);
121 lastState
= styler
.StyleAt(i
-1);
122 if (lastState
!= SCE_CSS_OPERATOR
&& lastState
!= SCE_CSS_COMMENT
)
128 if (lastState
== SCE_CSS_DEFAULT
)
129 sc
.SetState(SCE_CSS_DIRECTIVE
);
133 if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_CLASS
|| lastState
== SCE_CSS_ID
||
134 lastState
== SCE_CSS_PSEUDOCLASS
|| lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| lastState
== SCE_CSS_UNKNOWN_PSEUDOCLASS
)
135 sc
.SetState(SCE_CSS_DEFAULT
);
138 if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_DEFAULT
|| lastState
== SCE_CSS_CLASS
|| lastState
== SCE_CSS_ID
||
139 lastState
== SCE_CSS_PSEUDOCLASS
|| lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| lastState
== SCE_CSS_UNKNOWN_PSEUDOCLASS
)
140 sc
.SetState(SCE_CSS_ATTRIBUTE
);
143 if (lastState
== SCE_CSS_ATTRIBUTE
)
144 sc
.SetState(SCE_CSS_TAG
);
147 if (lastState
== SCE_CSS_MEDIA
)
148 sc
.SetState(SCE_CSS_DEFAULT
);
149 else if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_DIRECTIVE
)
150 sc
.SetState(SCE_CSS_IDENTIFIER
);
153 if (lastState
== SCE_CSS_DEFAULT
|| lastState
== SCE_CSS_VALUE
|| lastState
== SCE_CSS_IMPORTANT
||
154 lastState
== SCE_CSS_IDENTIFIER
|| lastState
== SCE_CSS_IDENTIFIER2
|| lastState
== SCE_CSS_IDENTIFIER3
)
155 sc
.SetState(SCE_CSS_DEFAULT
);
158 if (lastState
== SCE_CSS_PSEUDOCLASS
)
159 sc
.SetState(SCE_CSS_TAG
);
160 else if (lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
)
161 sc
.SetState(SCE_CSS_EXTENDED_PSEUDOCLASS
);
164 if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_DEFAULT
|| lastState
== SCE_CSS_CLASS
|| lastState
== SCE_CSS_ID
||
165 lastState
== SCE_CSS_PSEUDOCLASS
|| lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| lastState
== SCE_CSS_UNKNOWN_PSEUDOCLASS
||
166 lastState
== SCE_CSS_PSEUDOELEMENT
|| lastState
== SCE_CSS_EXTENDED_PSEUDOELEMENT
)
167 sc
.SetState(SCE_CSS_TAG
);
170 if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_DEFAULT
|| lastState
== SCE_CSS_CLASS
|| lastState
== SCE_CSS_ID
||
171 lastState
== SCE_CSS_PSEUDOCLASS
|| lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| lastState
== SCE_CSS_UNKNOWN_PSEUDOCLASS
||
172 lastState
== SCE_CSS_PSEUDOELEMENT
|| lastState
== SCE_CSS_EXTENDED_PSEUDOELEMENT
)
173 sc
.SetState(SCE_CSS_PSEUDOCLASS
);
174 else if (lastState
== SCE_CSS_IDENTIFIER
|| lastState
== SCE_CSS_IDENTIFIER2
||
175 lastState
== SCE_CSS_IDENTIFIER3
|| lastState
== SCE_CSS_EXTENDED_IDENTIFIER
||
176 lastState
== SCE_CSS_UNKNOWN_IDENTIFIER
)
177 sc
.SetState(SCE_CSS_VALUE
);
180 if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_DEFAULT
|| lastState
== SCE_CSS_CLASS
|| lastState
== SCE_CSS_ID
||
181 lastState
== SCE_CSS_PSEUDOCLASS
|| lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| lastState
== SCE_CSS_UNKNOWN_PSEUDOCLASS
)
182 sc
.SetState(SCE_CSS_CLASS
);
185 if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_DEFAULT
|| lastState
== SCE_CSS_CLASS
|| lastState
== SCE_CSS_ID
||
186 lastState
== SCE_CSS_PSEUDOCLASS
|| lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| lastState
== SCE_CSS_UNKNOWN_PSEUDOCLASS
)
187 sc
.SetState(SCE_CSS_ID
);
192 if (lastState
== SCE_CSS_TAG
)
193 sc
.SetState(SCE_CSS_DEFAULT
);
196 if (lastState
== SCE_CSS_DIRECTIVE
)
197 sc
.SetState(SCE_CSS_DEFAULT
);
198 else if (lastState
== SCE_CSS_VALUE
|| lastState
== SCE_CSS_IMPORTANT
)
199 sc
.SetState(SCE_CSS_IDENTIFIER
);
202 if (lastState
== SCE_CSS_VALUE
)
203 sc
.SetState(SCE_CSS_IMPORTANT
);
208 if (IsAWordChar(sc
.ch
)) {
209 if (sc
.state
== SCE_CSS_DEFAULT
)
210 sc
.SetState(SCE_CSS_TAG
);
214 if (sc
.ch
== '*' && sc
.state
== SCE_CSS_DEFAULT
) {
215 sc
.SetState(SCE_CSS_TAG
);
219 if (IsAWordChar(sc
.chPrev
) && (
220 sc
.state
== SCE_CSS_IDENTIFIER
|| sc
.state
== SCE_CSS_IDENTIFIER2
||
221 sc
.state
== SCE_CSS_IDENTIFIER3
|| sc
.state
== SCE_CSS_EXTENDED_IDENTIFIER
||
222 sc
.state
== SCE_CSS_UNKNOWN_IDENTIFIER
||
223 sc
.state
== SCE_CSS_PSEUDOCLASS
|| sc
.state
== SCE_CSS_PSEUDOELEMENT
||
224 sc
.state
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| sc
.state
== SCE_CSS_EXTENDED_PSEUDOELEMENT
||
225 sc
.state
== SCE_CSS_UNKNOWN_PSEUDOCLASS
||
226 sc
.state
== SCE_CSS_IMPORTANT
||
227 sc
.state
== SCE_CSS_DIRECTIVE
230 sc
.GetCurrentLowered(s
, sizeof(s
));
232 while (*s2
&& !IsAWordChar(*s2
))
235 case SCE_CSS_IDENTIFIER
:
236 case SCE_CSS_IDENTIFIER2
:
237 case SCE_CSS_IDENTIFIER3
:
238 case SCE_CSS_EXTENDED_IDENTIFIER
:
239 case SCE_CSS_UNKNOWN_IDENTIFIER
:
240 if (css1Props
.InList(s2
))
241 sc
.ChangeState(SCE_CSS_IDENTIFIER
);
242 else if (css2Props
.InList(s2
))
243 sc
.ChangeState(SCE_CSS_IDENTIFIER2
);
244 else if (css3Props
.InList(s2
))
245 sc
.ChangeState(SCE_CSS_IDENTIFIER3
);
246 else if (exProps
.InList(s2
))
247 sc
.ChangeState(SCE_CSS_EXTENDED_IDENTIFIER
);
249 sc
.ChangeState(SCE_CSS_UNKNOWN_IDENTIFIER
);
251 case SCE_CSS_PSEUDOCLASS
:
252 case SCE_CSS_PSEUDOELEMENT
:
253 case SCE_CSS_EXTENDED_PSEUDOCLASS
:
254 case SCE_CSS_EXTENDED_PSEUDOELEMENT
:
255 case SCE_CSS_UNKNOWN_PSEUDOCLASS
:
256 if (op
== ':' && opPrev
!= ':' && pseudoClasses
.InList(s2
))
257 sc
.ChangeState(SCE_CSS_PSEUDOCLASS
);
258 else if (opPrev
== ':' && pseudoElements
.InList(s2
))
259 sc
.ChangeState(SCE_CSS_PSEUDOELEMENT
);
260 else if ((op
== ':' || (op
== '(' && lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
)) && opPrev
!= ':' && exPseudoClasses
.InList(s2
))
261 sc
.ChangeState(SCE_CSS_EXTENDED_PSEUDOCLASS
);
262 else if (opPrev
== ':' && exPseudoElements
.InList(s2
))
263 sc
.ChangeState(SCE_CSS_EXTENDED_PSEUDOELEMENT
);
265 sc
.ChangeState(SCE_CSS_UNKNOWN_PSEUDOCLASS
);
267 case SCE_CSS_IMPORTANT
:
268 if (strcmp(s2
, "important") != 0)
269 sc
.ChangeState(SCE_CSS_VALUE
);
271 case SCE_CSS_DIRECTIVE
:
272 if (op
== '@' && strcmp(s2
, "media") == 0)
273 sc
.ChangeState(SCE_CSS_MEDIA
);
278 if (sc
.ch
!= '.' && sc
.ch
!= ':' && sc
.ch
!= '#' && (
279 sc
.state
== SCE_CSS_CLASS
|| sc
.state
== SCE_CSS_ID
||
280 (sc
.ch
!= '(' && sc
.ch
!= ')' && ( /* This line of the condition makes it possible to extend pseudo-classes with parentheses */
281 sc
.state
== SCE_CSS_PSEUDOCLASS
|| sc
.state
== SCE_CSS_PSEUDOELEMENT
||
282 sc
.state
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| sc
.state
== SCE_CSS_EXTENDED_PSEUDOELEMENT
||
283 sc
.state
== SCE_CSS_UNKNOWN_PSEUDOCLASS
286 sc
.SetState(SCE_CSS_TAG
);
288 if (sc
.Match('/', '*')) {
289 lastStateC
= sc
.state
;
290 sc
.SetState(SCE_CSS_COMMENT
);
292 } else if ((sc
.state
== SCE_CSS_VALUE
|| sc
.state
== SCE_CSS_ATTRIBUTE
)
293 && (sc
.ch
== '\"' || sc
.ch
== '\'')) {
294 lastStateS
= sc
.state
;
295 sc
.SetState((sc
.ch
== '\"' ? SCE_CSS_DOUBLESTRING
: SCE_CSS_SINGLESTRING
));
296 } else if (IsCssOperator(sc
.ch
)
297 && (sc
.state
!= SCE_CSS_ATTRIBUTE
|| sc
.ch
== ']')
298 && (sc
.state
!= SCE_CSS_VALUE
|| sc
.ch
== ';' || sc
.ch
== '}' || sc
.ch
== '!')
299 && ((sc
.state
!= SCE_CSS_DIRECTIVE
&& sc
.state
!= SCE_CSS_MEDIA
) || sc
.ch
== ';' || sc
.ch
== '{')
301 if (sc
.state
!= SCE_CSS_OPERATOR
)
302 lastState
= sc
.state
;
303 sc
.SetState(SCE_CSS_OPERATOR
);
312 static void FoldCSSDoc(unsigned int startPos
, int length
, int, WordList
*[], Accessor
&styler
) {
313 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
314 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
315 unsigned int endPos
= startPos
+ length
;
316 int visibleChars
= 0;
317 int lineCurrent
= styler
.GetLine(startPos
);
318 int levelPrev
= styler
.LevelAt(lineCurrent
) & SC_FOLDLEVELNUMBERMASK
;
319 int levelCurrent
= levelPrev
;
320 char chNext
= styler
[startPos
];
321 bool inComment
= (styler
.StyleAt(startPos
-1) == SCE_CSS_COMMENT
);
322 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
324 chNext
= styler
.SafeGetCharAt(i
+ 1);
325 int style
= styler
.StyleAt(i
);
326 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
328 if (!inComment
&& (style
== SCE_CSS_COMMENT
))
330 else if (inComment
&& (style
!= SCE_CSS_COMMENT
))
332 inComment
= (style
== SCE_CSS_COMMENT
);
334 if (style
== SCE_CSS_OPERATOR
) {
337 } else if (ch
== '}') {
343 if (visibleChars
== 0 && foldCompact
)
344 lev
|= SC_FOLDLEVELWHITEFLAG
;
345 if ((levelCurrent
> levelPrev
) && (visibleChars
> 0))
346 lev
|= SC_FOLDLEVELHEADERFLAG
;
347 if (lev
!= styler
.LevelAt(lineCurrent
)) {
348 styler
.SetLevel(lineCurrent
, lev
);
351 levelPrev
= levelCurrent
;
354 if (!isspacechar(ch
))
357 // Fill in the real level of the next line, keeping the current flags as they will be filled in later
358 int flagsNext
= styler
.LevelAt(lineCurrent
) & ~SC_FOLDLEVELNUMBERMASK
;
359 styler
.SetLevel(lineCurrent
, levelPrev
| flagsNext
);
362 static const char * const cssWordListDesc
[] = {
368 "Browser-Specific CSS Properties",
369 "Browser-Specific Pseudo-classes",
370 "Browser-Specific Pseudo-elements",
374 LexerModule
lmCss(SCLEX_CSS
, ColouriseCssDoc
, "css", FoldCSSDoc
, cssWordListDesc
);