Set release date.
[geany-mirror.git] / scintilla / LexCSS.cxx
blobf63103de3a8e381f73cdb7e974d7b49f3b439246
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 <ctype.h>
13 #include <stdio.h>
14 #include <stdarg.h>
16 #include "Platform.h"
18 #include "PropSet.h"
19 #include "Accessor.h"
20 #include "StyleContext.h"
21 #include "KeyWords.h"
22 #include "Scintilla.h"
23 #include "SciLexer.h"
25 #ifdef SCI_NAMESPACE
26 using namespace Scintilla;
27 #endif
30 static inline bool IsAWordChar(const unsigned int ch) {
31 /* FIXME:
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 == '@' ||
43 /* CSS2 */
44 ch == '*' || ch == '>' || ch == '+' || ch == '=' || ch == '~' || ch == '|' ||
45 ch == '[' || ch == ']' || ch == '(' || ch == ')')) {
46 return true;
48 return false;
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;
75 for (; i > 0; i--) {
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);
80 while (--i) {
81 lastState = styler.StyleAt(i-1);
82 if (lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT)
83 break;
85 if (i == 0)
86 lastState = SCE_CSS_DEFAULT;
88 break;
91 if (i == 0)
92 lastStateC = SCE_CSS_DEFAULT;
94 sc.Forward();
95 sc.ForwardSetState(lastStateC);
98 if (sc.state == SCE_CSS_COMMENT)
99 continue;
101 if (sc.state == SCE_CSS_DOUBLESTRING || sc.state == SCE_CSS_SINGLESTRING) {
102 if (sc.ch != (sc.state == SCE_CSS_DOUBLESTRING ? '\"' : '\''))
103 continue;
104 unsigned int i = sc.currentPos;
105 while (i && styler[i-1] == '\\')
106 i--;
107 if ((sc.currentPos - i) % 2 == 1)
108 continue;
109 sc.ForwardSetState(lastStateS);
112 if (sc.state == SCE_CSS_OPERATOR) {
113 if (op == ' ') {
114 unsigned int i = startPos;
115 op = styler.SafeGetCharAt(i-1);
116 opPrev = styler.SafeGetCharAt(i-2);
117 while (--i) {
118 lastState = styler.StyleAt(i-1);
119 if (lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT)
120 break;
123 switch (op) {
124 case '@':
125 if (lastState == SCE_CSS_DEFAULT)
126 sc.SetState(SCE_CSS_DIRECTIVE);
127 break;
128 case '>':
129 case '+':
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);
133 break;
134 case '[':
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);
138 break;
139 case ']':
140 if (lastState == SCE_CSS_ATTRIBUTE)
141 sc.SetState(SCE_CSS_TAG);
142 break;
143 case '{':
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);
148 break;
149 case '}':
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);
153 break;
154 case '(':
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);
159 break;
160 case ')':
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);
165 break;
166 case ':':
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);
175 break;
176 case '.':
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);
180 break;
181 case '#':
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);
185 break;
186 case ',':
187 case '|':
188 case '~':
189 if (lastState == SCE_CSS_TAG)
190 sc.SetState(SCE_CSS_DEFAULT);
191 break;
192 case ';':
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);
197 break;
198 case '!':
199 if (lastState == SCE_CSS_VALUE)
200 sc.SetState(SCE_CSS_IMPORTANT);
201 break;
205 if (IsAWordChar(sc.ch)) {
206 if (sc.state == SCE_CSS_DEFAULT)
207 sc.SetState(SCE_CSS_TAG);
208 continue;
211 if (sc.ch == '*' && sc.state == SCE_CSS_DEFAULT) {
212 sc.SetState(SCE_CSS_TAG);
213 continue;
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
225 )) {
226 char s[100];
227 sc.GetCurrentLowered(s, sizeof(s));
228 char *s2 = s;
229 while (*s2 && !IsAWordChar(*s2))
230 s2++;
231 switch (sc.state) {
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);
245 else
246 sc.ChangeState(SCE_CSS_UNKNOWN_IDENTIFIER);
247 break;
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);
261 else
262 sc.ChangeState(SCE_CSS_UNKNOWN_PSEUDOCLASS);
263 break;
264 case SCE_CSS_IMPORTANT:
265 if (strcmp(s2, "important") != 0)
266 sc.ChangeState(SCE_CSS_VALUE);
267 break;
268 case SCE_CSS_DIRECTIVE:
269 if (op == '@' && strcmp(s2, "media") == 0)
270 sc.ChangeState(SCE_CSS_MEDIA);
271 break;
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);
288 sc.Forward();
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);
301 op = sc.ch;
302 opPrev = sc.chPrev;
306 sc.Complete();
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++) {
320 char ch = chNext;
321 chNext = styler.SafeGetCharAt(i + 1);
322 int style = styler.StyleAt(i);
323 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
324 if (foldComment) {
325 if (!inComment && (style == SCE_CSS_COMMENT))
326 levelCurrent++;
327 else if (inComment && (style != SCE_CSS_COMMENT))
328 levelCurrent--;
329 inComment = (style == SCE_CSS_COMMENT);
331 if (style == SCE_CSS_OPERATOR) {
332 if (ch == '{') {
333 levelCurrent++;
334 } else if (ch == '}') {
335 levelCurrent--;
338 if (atEOL) {
339 int lev = levelPrev;
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);
347 lineCurrent++;
348 levelPrev = levelCurrent;
349 visibleChars = 0;
351 if (!isspacechar(ch))
352 visibleChars++;
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[] = {
360 "CSS1 Properties",
361 "Pseudo-classes",
362 "CSS2 Properties",
363 "CSS3 Properties",
364 "Pseudo-elements",
365 "Browser-Specific CSS Properties",
366 "Browser-Specific Pseudo-classes",
367 "Browser-Specific Pseudo-elements",
371 LexerModule lmCss(SCLEX_CSS, ColouriseCssDoc, "css", FoldCSSDoc, cssWordListDesc);