updated Scintilla to 2.29
[TortoiseGit.git] / ext / scintilla / lexers / LexCSS.cxx
blob17e8a8f9d6ef887cd89f91042793c041c1d6170d
1 // Scintilla source code edit control
2 /** @file LexCSS.cxx
3 ** Lexer for Cascading Style Sheets
4 ** Written by Jakub Vrána
5 ** Improved by Philippe Lhoste (CSS2)
6 **/
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.
10 #include <stdlib.h>
11 #include <string.h>
12 #include <stdio.h>
13 #include <stdarg.h>
14 #include <assert.h>
15 #include <ctype.h>
17 #include "ILexer.h"
18 #include "Scintilla.h"
19 #include "SciLexer.h"
21 #include "WordList.h"
22 #include "LexAccessor.h"
23 #include "Accessor.h"
24 #include "StyleContext.h"
25 #include "CharacterSet.h"
26 #include "LexerModule.h"
28 #ifdef SCI_NAMESPACE
29 using namespace Scintilla;
30 #endif
33 static inline bool IsAWordChar(const unsigned int ch) {
34 /* FIXME:
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 == '@' ||
46 /* CSS2 */
47 ch == '*' || ch == '>' || ch == '+' || ch == '=' || ch == '~' || ch == '|' ||
48 ch == '[' || ch == ']' || ch == '(' || ch == ')')) {
49 return true;
51 return false;
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;
78 for (; i > 0; i--) {
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);
83 while (--i) {
84 lastState = styler.StyleAt(i-1);
85 if (lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT)
86 break;
88 if (i == 0)
89 lastState = SCE_CSS_DEFAULT;
91 break;
94 if (i == 0)
95 lastStateC = SCE_CSS_DEFAULT;
97 sc.Forward();
98 sc.ForwardSetState(lastStateC);
101 if (sc.state == SCE_CSS_COMMENT)
102 continue;
104 if (sc.state == SCE_CSS_DOUBLESTRING || sc.state == SCE_CSS_SINGLESTRING) {
105 if (sc.ch != (sc.state == SCE_CSS_DOUBLESTRING ? '\"' : '\''))
106 continue;
107 unsigned int i = sc.currentPos;
108 while (i && styler[i-1] == '\\')
109 i--;
110 if ((sc.currentPos - i) % 2 == 1)
111 continue;
112 sc.ForwardSetState(lastStateS);
115 if (sc.state == SCE_CSS_OPERATOR) {
116 if (op == ' ') {
117 unsigned int i = startPos;
118 op = styler.SafeGetCharAt(i-1);
119 opPrev = styler.SafeGetCharAt(i-2);
120 while (--i) {
121 lastState = styler.StyleAt(i-1);
122 if (lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT)
123 break;
126 switch (op) {
127 case '@':
128 if (lastState == SCE_CSS_DEFAULT)
129 sc.SetState(SCE_CSS_DIRECTIVE);
130 break;
131 case '>':
132 case '+':
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);
136 break;
137 case '[':
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);
141 break;
142 case ']':
143 if (lastState == SCE_CSS_ATTRIBUTE)
144 sc.SetState(SCE_CSS_TAG);
145 break;
146 case '{':
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);
151 break;
152 case '}':
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);
156 break;
157 case '(':
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);
162 break;
163 case ')':
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);
168 break;
169 case ':':
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);
178 break;
179 case '.':
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);
183 break;
184 case '#':
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);
188 break;
189 case ',':
190 case '|':
191 case '~':
192 if (lastState == SCE_CSS_TAG)
193 sc.SetState(SCE_CSS_DEFAULT);
194 break;
195 case ';':
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);
200 break;
201 case '!':
202 if (lastState == SCE_CSS_VALUE)
203 sc.SetState(SCE_CSS_IMPORTANT);
204 break;
208 if (IsAWordChar(sc.ch)) {
209 if (sc.state == SCE_CSS_DEFAULT)
210 sc.SetState(SCE_CSS_TAG);
211 continue;
214 if (sc.ch == '*' && sc.state == SCE_CSS_DEFAULT) {
215 sc.SetState(SCE_CSS_TAG);
216 continue;
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
228 )) {
229 char s[100];
230 sc.GetCurrentLowered(s, sizeof(s));
231 char *s2 = s;
232 while (*s2 && !IsAWordChar(*s2))
233 s2++;
234 switch (sc.state) {
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);
248 else
249 sc.ChangeState(SCE_CSS_UNKNOWN_IDENTIFIER);
250 break;
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);
264 else
265 sc.ChangeState(SCE_CSS_UNKNOWN_PSEUDOCLASS);
266 break;
267 case SCE_CSS_IMPORTANT:
268 if (strcmp(s2, "important") != 0)
269 sc.ChangeState(SCE_CSS_VALUE);
270 break;
271 case SCE_CSS_DIRECTIVE:
272 if (op == '@' && strcmp(s2, "media") == 0)
273 sc.ChangeState(SCE_CSS_MEDIA);
274 break;
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);
291 sc.Forward();
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);
304 op = sc.ch;
305 opPrev = sc.chPrev;
309 sc.Complete();
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++) {
323 char ch = chNext;
324 chNext = styler.SafeGetCharAt(i + 1);
325 int style = styler.StyleAt(i);
326 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
327 if (foldComment) {
328 if (!inComment && (style == SCE_CSS_COMMENT))
329 levelCurrent++;
330 else if (inComment && (style != SCE_CSS_COMMENT))
331 levelCurrent--;
332 inComment = (style == SCE_CSS_COMMENT);
334 if (style == SCE_CSS_OPERATOR) {
335 if (ch == '{') {
336 levelCurrent++;
337 } else if (ch == '}') {
338 levelCurrent--;
341 if (atEOL) {
342 int lev = levelPrev;
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);
350 lineCurrent++;
351 levelPrev = levelCurrent;
352 visibleChars = 0;
354 if (!isspacechar(ch))
355 visibleChars++;
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[] = {
363 "CSS1 Properties",
364 "Pseudo-classes",
365 "CSS2 Properties",
366 "CSS3 Properties",
367 "Pseudo-elements",
368 "Browser-Specific CSS Properties",
369 "Browser-Specific Pseudo-classes",
370 "Browser-Specific Pseudo-elements",
374 LexerModule lmCss(SCLEX_CSS, ColouriseCssDoc, "css", FoldCSSDoc, cssWordListDesc);