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.
20 #include "StyleContext.h"
22 #include "Scintilla.h"
26 using namespace Scintilla
;
30 static inline bool IsAWordChar(const unsigned int ch
) {
32 * The CSS spec allows "ISO 10646 characters U+00A1 and higher" to be treated as word chars.
33 * Unfortunately, we are only getting string bytes here, and not full unicode characters. We cannot guarantee
34 * that our byte is between U+0080 - U+00A0 (to return false), so we have to allow all characters U+0080 and higher
36 return ch
>= 0x80 || isalnum(ch
) || ch
== '-' || ch
== '_';
39 inline bool IsCssOperator(const int ch
) {
40 if (!((ch
< 0x80) && isalnum(ch
)) &&
41 (ch
== '{' || ch
== '}' || ch
== ':' || ch
== ',' || ch
== ';' ||
42 ch
== '.' || ch
== '#' || ch
== '!' || ch
== '@' ||
44 ch
== '*' || ch
== '>' || ch
== '+' || ch
== '=' || ch
== '~' || ch
== '|' ||
45 ch
== '[' || ch
== ']' || ch
== '(' || ch
== ')')) {
51 static void ColouriseCssDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[], Accessor
&styler
) {
52 WordList
&css1Props
= *keywordlists
[0];
53 WordList
&pseudoClasses
= *keywordlists
[1];
54 WordList
&css2Props
= *keywordlists
[2];
55 WordList
&css3Props
= *keywordlists
[3];
56 WordList
&pseudoElements
= *keywordlists
[4];
57 WordList
&exProps
= *keywordlists
[5];
58 WordList
&exPseudoClasses
= *keywordlists
[6];
59 WordList
&exPseudoElements
= *keywordlists
[7];
61 StyleContext
sc(startPos
, length
, initStyle
, styler
);
63 int lastState
= -1; // before operator
64 int lastStateC
= -1; // before comment
65 int lastStateS
= -1; // before single-quoted/double-quoted string
66 int op
= ' '; // last operator
67 int opPrev
= ' '; // last operator
69 for (; sc
.More(); sc
.Forward()) {
70 if (sc
.state
== SCE_CSS_COMMENT
&& sc
.Match('*', '/')) {
71 if (lastStateC
== -1) {
72 // backtrack to get last state:
73 // comments are like whitespace, so we must return to the previous state
74 unsigned int i
= startPos
;
76 if ((lastStateC
= styler
.StyleAt(i
-1)) != SCE_CSS_COMMENT
) {
77 if (lastStateC
== SCE_CSS_OPERATOR
) {
78 op
= styler
.SafeGetCharAt(i
-1);
79 opPrev
= styler
.SafeGetCharAt(i
-2);
81 lastState
= styler
.StyleAt(i
-1);
82 if (lastState
!= SCE_CSS_OPERATOR
&& lastState
!= SCE_CSS_COMMENT
)
86 lastState
= SCE_CSS_DEFAULT
;
92 lastStateC
= SCE_CSS_DEFAULT
;
95 sc
.ForwardSetState(lastStateC
);
98 if (sc
.state
== SCE_CSS_COMMENT
)
101 if (sc
.state
== SCE_CSS_DOUBLESTRING
|| sc
.state
== SCE_CSS_SINGLESTRING
) {
102 if (sc
.ch
!= (sc
.state
== SCE_CSS_DOUBLESTRING
? '\"' : '\''))
104 unsigned int i
= sc
.currentPos
;
105 while (i
&& styler
[i
-1] == '\\')
107 if ((sc
.currentPos
- i
) % 2 == 1)
109 sc
.ForwardSetState(lastStateS
);
112 if (sc
.state
== SCE_CSS_OPERATOR
) {
114 unsigned int i
= startPos
;
115 op
= styler
.SafeGetCharAt(i
-1);
116 opPrev
= styler
.SafeGetCharAt(i
-2);
118 lastState
= styler
.StyleAt(i
-1);
119 if (lastState
!= SCE_CSS_OPERATOR
&& lastState
!= SCE_CSS_COMMENT
)
125 if (lastState
== SCE_CSS_DEFAULT
)
126 sc
.SetState(SCE_CSS_DIRECTIVE
);
130 if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_CLASS
|| lastState
== SCE_CSS_ID
||
131 lastState
== SCE_CSS_PSEUDOCLASS
|| lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| lastState
== SCE_CSS_UNKNOWN_PSEUDOCLASS
)
132 sc
.SetState(SCE_CSS_DEFAULT
);
135 if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_DEFAULT
|| lastState
== SCE_CSS_CLASS
|| lastState
== SCE_CSS_ID
||
136 lastState
== SCE_CSS_PSEUDOCLASS
|| lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| lastState
== SCE_CSS_UNKNOWN_PSEUDOCLASS
)
137 sc
.SetState(SCE_CSS_ATTRIBUTE
);
140 if (lastState
== SCE_CSS_ATTRIBUTE
)
141 sc
.SetState(SCE_CSS_TAG
);
144 if (lastState
== SCE_CSS_MEDIA
)
145 sc
.SetState(SCE_CSS_DEFAULT
);
146 else if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_DIRECTIVE
)
147 sc
.SetState(SCE_CSS_IDENTIFIER
);
150 if (lastState
== SCE_CSS_DEFAULT
|| lastState
== SCE_CSS_VALUE
|| lastState
== SCE_CSS_IMPORTANT
||
151 lastState
== SCE_CSS_IDENTIFIER
|| lastState
== SCE_CSS_IDENTIFIER2
|| lastState
== SCE_CSS_IDENTIFIER3
)
152 sc
.SetState(SCE_CSS_DEFAULT
);
155 if (lastState
== SCE_CSS_PSEUDOCLASS
)
156 sc
.SetState(SCE_CSS_TAG
);
157 else if (lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
)
158 sc
.SetState(SCE_CSS_EXTENDED_PSEUDOCLASS
);
161 if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_DEFAULT
|| lastState
== SCE_CSS_CLASS
|| lastState
== SCE_CSS_ID
||
162 lastState
== SCE_CSS_PSEUDOCLASS
|| lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| lastState
== SCE_CSS_UNKNOWN_PSEUDOCLASS
||
163 lastState
== SCE_CSS_PSEUDOELEMENT
|| lastState
== SCE_CSS_EXTENDED_PSEUDOELEMENT
)
164 sc
.SetState(SCE_CSS_TAG
);
167 if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_DEFAULT
|| lastState
== SCE_CSS_CLASS
|| lastState
== SCE_CSS_ID
||
168 lastState
== SCE_CSS_PSEUDOCLASS
|| lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| lastState
== SCE_CSS_UNKNOWN_PSEUDOCLASS
||
169 lastState
== SCE_CSS_PSEUDOELEMENT
|| lastState
== SCE_CSS_EXTENDED_PSEUDOELEMENT
)
170 sc
.SetState(SCE_CSS_PSEUDOCLASS
);
171 else if (lastState
== SCE_CSS_IDENTIFIER
|| lastState
== SCE_CSS_IDENTIFIER2
||
172 lastState
== SCE_CSS_IDENTIFIER3
|| lastState
== SCE_CSS_EXTENDED_IDENTIFIER
||
173 lastState
== SCE_CSS_UNKNOWN_IDENTIFIER
)
174 sc
.SetState(SCE_CSS_VALUE
);
177 if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_DEFAULT
|| lastState
== SCE_CSS_CLASS
|| lastState
== SCE_CSS_ID
||
178 lastState
== SCE_CSS_PSEUDOCLASS
|| lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| lastState
== SCE_CSS_UNKNOWN_PSEUDOCLASS
)
179 sc
.SetState(SCE_CSS_CLASS
);
182 if (lastState
== SCE_CSS_TAG
|| lastState
== SCE_CSS_DEFAULT
|| lastState
== SCE_CSS_CLASS
|| lastState
== SCE_CSS_ID
||
183 lastState
== SCE_CSS_PSEUDOCLASS
|| lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| lastState
== SCE_CSS_UNKNOWN_PSEUDOCLASS
)
184 sc
.SetState(SCE_CSS_ID
);
189 if (lastState
== SCE_CSS_TAG
)
190 sc
.SetState(SCE_CSS_DEFAULT
);
193 if (lastState
== SCE_CSS_DIRECTIVE
)
194 sc
.SetState(SCE_CSS_DEFAULT
);
195 else if (lastState
== SCE_CSS_VALUE
|| lastState
== SCE_CSS_IMPORTANT
)
196 sc
.SetState(SCE_CSS_IDENTIFIER
);
199 if (lastState
== SCE_CSS_VALUE
)
200 sc
.SetState(SCE_CSS_IMPORTANT
);
205 if (IsAWordChar(sc
.ch
)) {
206 if (sc
.state
== SCE_CSS_DEFAULT
)
207 sc
.SetState(SCE_CSS_TAG
);
211 if (sc
.ch
== '*' && sc
.state
== SCE_CSS_DEFAULT
) {
212 sc
.SetState(SCE_CSS_TAG
);
216 if (IsAWordChar(sc
.chPrev
) && (
217 sc
.state
== SCE_CSS_IDENTIFIER
|| sc
.state
== SCE_CSS_IDENTIFIER2
||
218 sc
.state
== SCE_CSS_IDENTIFIER3
|| sc
.state
== SCE_CSS_EXTENDED_IDENTIFIER
||
219 sc
.state
== SCE_CSS_UNKNOWN_IDENTIFIER
||
220 sc
.state
== SCE_CSS_PSEUDOCLASS
|| sc
.state
== SCE_CSS_PSEUDOELEMENT
||
221 sc
.state
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| sc
.state
== SCE_CSS_EXTENDED_PSEUDOELEMENT
||
222 sc
.state
== SCE_CSS_UNKNOWN_PSEUDOCLASS
||
223 sc
.state
== SCE_CSS_IMPORTANT
||
224 sc
.state
== SCE_CSS_DIRECTIVE
227 sc
.GetCurrentLowered(s
, sizeof(s
));
229 while (*s2
&& !IsAWordChar(*s2
))
232 case SCE_CSS_IDENTIFIER
:
233 case SCE_CSS_IDENTIFIER2
:
234 case SCE_CSS_IDENTIFIER3
:
235 case SCE_CSS_EXTENDED_IDENTIFIER
:
236 case SCE_CSS_UNKNOWN_IDENTIFIER
:
237 if (css1Props
.InList(s2
))
238 sc
.ChangeState(SCE_CSS_IDENTIFIER
);
239 else if (css2Props
.InList(s2
))
240 sc
.ChangeState(SCE_CSS_IDENTIFIER2
);
241 else if (css3Props
.InList(s2
))
242 sc
.ChangeState(SCE_CSS_IDENTIFIER3
);
243 else if (exProps
.InList(s2
))
244 sc
.ChangeState(SCE_CSS_EXTENDED_IDENTIFIER
);
246 sc
.ChangeState(SCE_CSS_UNKNOWN_IDENTIFIER
);
248 case SCE_CSS_PSEUDOCLASS
:
249 case SCE_CSS_PSEUDOELEMENT
:
250 case SCE_CSS_EXTENDED_PSEUDOCLASS
:
251 case SCE_CSS_EXTENDED_PSEUDOELEMENT
:
252 case SCE_CSS_UNKNOWN_PSEUDOCLASS
:
253 if (op
== ':' && opPrev
!= ':' && pseudoClasses
.InList(s2
))
254 sc
.ChangeState(SCE_CSS_PSEUDOCLASS
);
255 else if (opPrev
== ':' && pseudoElements
.InList(s2
))
256 sc
.ChangeState(SCE_CSS_PSEUDOELEMENT
);
257 else if ((op
== ':' || (op
== '(' && lastState
== SCE_CSS_EXTENDED_PSEUDOCLASS
)) && opPrev
!= ':' && exPseudoClasses
.InList(s2
))
258 sc
.ChangeState(SCE_CSS_EXTENDED_PSEUDOCLASS
);
259 else if (opPrev
== ':' && exPseudoElements
.InList(s2
))
260 sc
.ChangeState(SCE_CSS_EXTENDED_PSEUDOELEMENT
);
262 sc
.ChangeState(SCE_CSS_UNKNOWN_PSEUDOCLASS
);
264 case SCE_CSS_IMPORTANT
:
265 if (strcmp(s2
, "important") != 0)
266 sc
.ChangeState(SCE_CSS_VALUE
);
268 case SCE_CSS_DIRECTIVE
:
269 if (op
== '@' && strcmp(s2
, "media") == 0)
270 sc
.ChangeState(SCE_CSS_MEDIA
);
275 if (sc
.ch
!= '.' && sc
.ch
!= ':' && sc
.ch
!= '#' && (
276 sc
.state
== SCE_CSS_CLASS
|| sc
.state
== SCE_CSS_ID
||
277 (sc
.ch
!= '(' && sc
.ch
!= ')' && ( /* This line of the condition makes it possible to extend pseudo-classes with parentheses */
278 sc
.state
== SCE_CSS_PSEUDOCLASS
|| sc
.state
== SCE_CSS_PSEUDOELEMENT
||
279 sc
.state
== SCE_CSS_EXTENDED_PSEUDOCLASS
|| sc
.state
== SCE_CSS_EXTENDED_PSEUDOELEMENT
||
280 sc
.state
== SCE_CSS_UNKNOWN_PSEUDOCLASS
283 sc
.SetState(SCE_CSS_TAG
);
285 if (sc
.Match('/', '*')) {
286 lastStateC
= sc
.state
;
287 sc
.SetState(SCE_CSS_COMMENT
);
289 } else if ((sc
.state
== SCE_CSS_VALUE
|| sc
.state
== SCE_CSS_ATTRIBUTE
)
290 && (sc
.ch
== '\"' || sc
.ch
== '\'')) {
291 lastStateS
= sc
.state
;
292 sc
.SetState((sc
.ch
== '\"' ? SCE_CSS_DOUBLESTRING
: SCE_CSS_SINGLESTRING
));
293 } else if (IsCssOperator(sc
.ch
)
294 && (sc
.state
!= SCE_CSS_ATTRIBUTE
|| sc
.ch
== ']')
295 && (sc
.state
!= SCE_CSS_VALUE
|| sc
.ch
== ';' || sc
.ch
== '}' || sc
.ch
== '!')
296 && ((sc
.state
!= SCE_CSS_DIRECTIVE
&& sc
.state
!= SCE_CSS_MEDIA
) || sc
.ch
== ';' || sc
.ch
== '{')
298 if (sc
.state
!= SCE_CSS_OPERATOR
)
299 lastState
= sc
.state
;
300 sc
.SetState(SCE_CSS_OPERATOR
);
309 static void FoldCSSDoc(unsigned int startPos
, int length
, int, WordList
*[], Accessor
&styler
) {
310 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
311 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
312 unsigned int endPos
= startPos
+ length
;
313 int visibleChars
= 0;
314 int lineCurrent
= styler
.GetLine(startPos
);
315 int levelPrev
= styler
.LevelAt(lineCurrent
) & SC_FOLDLEVELNUMBERMASK
;
316 int levelCurrent
= levelPrev
;
317 char chNext
= styler
[startPos
];
318 bool inComment
= (styler
.StyleAt(startPos
-1) == SCE_CSS_COMMENT
);
319 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
321 chNext
= styler
.SafeGetCharAt(i
+ 1);
322 int style
= styler
.StyleAt(i
);
323 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
325 if (!inComment
&& (style
== SCE_CSS_COMMENT
))
327 else if (inComment
&& (style
!= SCE_CSS_COMMENT
))
329 inComment
= (style
== SCE_CSS_COMMENT
);
331 if (style
== SCE_CSS_OPERATOR
) {
334 } else if (ch
== '}') {
340 if (visibleChars
== 0 && foldCompact
)
341 lev
|= SC_FOLDLEVELWHITEFLAG
;
342 if ((levelCurrent
> levelPrev
) && (visibleChars
> 0))
343 lev
|= SC_FOLDLEVELHEADERFLAG
;
344 if (lev
!= styler
.LevelAt(lineCurrent
)) {
345 styler
.SetLevel(lineCurrent
, lev
);
348 levelPrev
= levelCurrent
;
351 if (!isspacechar(ch
))
354 // Fill in the real level of the next line, keeping the current flags as they will be filled in later
355 int flagsNext
= styler
.LevelAt(lineCurrent
) & ~SC_FOLDLEVELNUMBERMASK
;
356 styler
.SetLevel(lineCurrent
, levelPrev
| flagsNext
);
359 static const char * const cssWordListDesc
[] = {
365 "Browser-Specific CSS Properties",
366 "Browser-Specific Pseudo-classes",
367 "Browser-Specific Pseudo-elements",
371 LexerModule
lmCss(SCLEX_CSS
, ColouriseCssDoc
, "css", FoldCSSDoc
, cssWordListDesc
);