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.
9
10 #include <stdlib.h>
11 #include <string.h>
12 #include <ctype.h>
13 #include <stdio.h>
14 #include <stdarg.h>
15
16 #include "Platform.h"
17
18 #include "PropSet.h"
19 #include "Accessor.h"
20 #include "StyleContext.h"
21 #include "KeyWords.h"
22 #include "Scintilla.h"
23 #include "SciLexer.h"
24
25
IsAWordChar(const unsigned int ch)26 static inline bool IsAWordChar(const unsigned int ch) {
27 return (isalnum(ch) || ch == '-' || ch == '_' || ch >= 161); // _ is not in fact correct CSS word-character
28 }
29
IsCssOperator(const char ch)30 inline bool IsCssOperator(const char ch) {
31 if (!isalnum(ch) &&
32 (ch == '{' || ch == '}' || ch == ':' || ch == ',' || ch == ';' ||
33 ch == '.' || ch == '#' || ch == '!' || ch == '@' ||
34 /* CSS2 */
35 ch == '*' || ch == '>' || ch == '+' || ch == '=' || ch == '~' || ch == '|' ||
36 ch == '[' || ch == ']' || ch == '(' || ch == ')')) {
37 return true;
38 }
39 return false;
40 }
41
ColouriseCssDoc(unsigned int startPos,int length,int initStyle,WordList * keywordlists[],Accessor & styler)42 static void ColouriseCssDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[], Accessor &styler) {
43 WordList &keywords = *keywordlists[0];
44 WordList &pseudoClasses = *keywordlists[1];
45 WordList &keywords2 = *keywordlists[2];
46
47 StyleContext sc(startPos, length, initStyle, styler);
48
49 int lastState = -1; // before operator
50 int lastStateC = -1; // before comment
51 int op = ' '; // last operator
52
53 for (; sc.More(); sc.Forward()) {
54 if (sc.state == SCE_CSS_COMMENT && sc.Match('*', '/')) {
55 if (lastStateC == -1) {
56 // backtrack to get last state:
57 // comments are like whitespace, so we must return to the previous state
58 unsigned int i = startPos;
59 for (; i > 0; i--) {
60 if ((lastStateC = styler.StyleAt(i-1)) != SCE_CSS_COMMENT) {
61 if (lastStateC == SCE_CSS_OPERATOR) {
62 op = styler.SafeGetCharAt(i-1);
63 while (--i) {
64 lastState = styler.StyleAt(i-1);
65 if (lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT)
66 break;
67 }
68 if (i == 0)
69 lastState = SCE_CSS_DEFAULT;
70 }
71 break;
72 }
73 }
74 if (i == 0)
75 lastStateC = SCE_CSS_DEFAULT;
76 }
77 sc.Forward();
78 sc.ForwardSetState(lastStateC);
79 }
80
81 if (sc.state == SCE_CSS_COMMENT)
82 continue;
83
84 if (sc.state == SCE_CSS_DOUBLESTRING || sc.state == SCE_CSS_SINGLESTRING) {
85 if (sc.ch != (sc.state == SCE_CSS_DOUBLESTRING ? '\"' : '\''))
86 continue;
87 unsigned int i = sc.currentPos;
88 while (i && styler[i-1] == '\\')
89 i--;
90 if ((sc.currentPos - i) % 2 == 1)
91 continue;
92 sc.ForwardSetState(SCE_CSS_VALUE);
93 }
94
95 if (sc.state == SCE_CSS_OPERATOR) {
96 if (op == ' ') {
97 unsigned int i = startPos;
98 op = styler.SafeGetCharAt(i-1);
99 while (--i) {
100 lastState = styler.StyleAt(i-1);
101 if (lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT)
102 break;
103 }
104 }
105 switch (op) {
106 case '@':
107 if (lastState == SCE_CSS_DEFAULT)
108 sc.SetState(SCE_CSS_DIRECTIVE);
109 break;
110 case '*':
111 if (lastState == SCE_CSS_DEFAULT)
112 sc.SetState(SCE_CSS_TAG);
113 break;
114 case '>':
115 case '+':
116 if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_CLASS
117 || lastState == SCE_CSS_ID || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
118 sc.SetState(SCE_CSS_DEFAULT);
119 break;
120 case '[':
121 if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_DEFAULT ||
122 lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
123 sc.SetState(SCE_CSS_ATTRIBUTE);
124 break;
125 case ']':
126 if (lastState == SCE_CSS_ATTRIBUTE)
127 sc.SetState(SCE_CSS_TAG);
128 break;
129 case '{':
130 if (lastState == SCE_CSS_DIRECTIVE)
131 sc.SetState(SCE_CSS_DEFAULT);
132 else if (lastState == SCE_CSS_TAG)
133 sc.SetState(SCE_CSS_IDENTIFIER);
134 break;
135 case '}':
136 if (lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_VALUE || lastState == SCE_CSS_IMPORTANT ||
137 lastState == SCE_CSS_IDENTIFIER || lastState == SCE_CSS_IDENTIFIER2)
138 sc.SetState(SCE_CSS_DEFAULT);
139 break;
140 case ':':
141 if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_DEFAULT ||
142 lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
143 sc.SetState(SCE_CSS_PSEUDOCLASS);
144 else if (lastState == SCE_CSS_IDENTIFIER || lastState == SCE_CSS_IDENTIFIER2 || lastState == SCE_CSS_UNKNOWN_IDENTIFIER)
145 sc.SetState(SCE_CSS_VALUE);
146 break;
147 case '.':
148 if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_DEFAULT ||
149 lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
150 sc.SetState(SCE_CSS_CLASS);
151 break;
152 case '#':
153 if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_DEFAULT ||
154 lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
155 sc.SetState(SCE_CSS_ID);
156 break;
157 case ',':
158 if (lastState == SCE_CSS_TAG)
159 sc.SetState(SCE_CSS_DEFAULT);
160 break;
161 case ';':
162 if (lastState == SCE_CSS_DIRECTIVE)
163 sc.SetState(SCE_CSS_DEFAULT);
164 else if (lastState == SCE_CSS_VALUE || lastState == SCE_CSS_IMPORTANT)
165 sc.SetState(SCE_CSS_IDENTIFIER);
166 break;
167 case '!':
168 if (lastState == SCE_CSS_VALUE)
169 sc.SetState(SCE_CSS_IMPORTANT);
170 break;
171 }
172 }
173
174 if (IsAWordChar(sc.ch)) {
175 if (sc.state == SCE_CSS_DEFAULT)
176 sc.SetState(SCE_CSS_TAG);
177 continue;
178 }
179
180 if (IsAWordChar(sc.chPrev) && (
181 sc.state == SCE_CSS_IDENTIFIER || sc.state == SCE_CSS_IDENTIFIER2
182 || sc.state == SCE_CSS_UNKNOWN_IDENTIFIER
183 || sc.state == SCE_CSS_PSEUDOCLASS || sc.state == SCE_CSS_UNKNOWN_PSEUDOCLASS
184 || sc.state == SCE_CSS_IMPORTANT
185 )) {
186 char s[100];
187 sc.GetCurrentLowered(s, sizeof(s));
188 char *s2 = s;
189 while (*s2 && !IsAWordChar(*s2))
190 s2++;
191 switch (sc.state) {
192 case SCE_CSS_IDENTIFIER:
193 if (!keywords.InList(s2)) {
194 if (keywords2.InList(s2)) {
195 sc.ChangeState(SCE_CSS_IDENTIFIER2);
196 } else {
197 sc.ChangeState(SCE_CSS_UNKNOWN_IDENTIFIER);
198 }
199 }
200 break;
201 case SCE_CSS_UNKNOWN_IDENTIFIER:
202 if (keywords.InList(s2))
203 sc.ChangeState(SCE_CSS_IDENTIFIER);
204 else if (keywords2.InList(s2))
205 sc.ChangeState(SCE_CSS_IDENTIFIER2);
206 break;
207 case SCE_CSS_PSEUDOCLASS:
208 if (!pseudoClasses.InList(s2))
209 sc.ChangeState(SCE_CSS_UNKNOWN_PSEUDOCLASS);
210 break;
211 case SCE_CSS_UNKNOWN_PSEUDOCLASS:
212 if (pseudoClasses.InList(s2))
213 sc.ChangeState(SCE_CSS_PSEUDOCLASS);
214 break;
215 case SCE_CSS_IMPORTANT:
216 if (strcmp(s2, "important") != 0)
217 sc.ChangeState(SCE_CSS_VALUE);
218 break;
219 }
220 }
221
222 if (sc.ch != '.' && sc.ch != ':' && sc.ch != '#' && (sc.state == SCE_CSS_CLASS || sc.state == SCE_CSS_PSEUDOCLASS || sc.state == SCE_CSS_UNKNOWN_PSEUDOCLASS || sc.state == SCE_CSS_ID))
223 sc.SetState(SCE_CSS_TAG);
224
225 if (sc.Match('/', '*')) {
226 lastStateC = sc.state;
227 sc.SetState(SCE_CSS_COMMENT);
228 sc.Forward();
229 } else if (sc.state == SCE_CSS_VALUE && (sc.ch == '\"' || sc.ch == '\'')) {
230 sc.SetState((sc.ch == '\"' ? SCE_CSS_DOUBLESTRING : SCE_CSS_SINGLESTRING));
231 } else if (IsCssOperator(static_cast<char>(sc.ch))
232 && (sc.state != SCE_CSS_ATTRIBUTE || sc.ch == ']')
233 && (sc.state != SCE_CSS_VALUE || sc.ch == ';' || sc.ch == '}' || sc.ch == '!')
234 && (sc.state != SCE_CSS_DIRECTIVE || sc.ch == ';' || sc.ch == '{')
235 ) {
236 if (sc.state != SCE_CSS_OPERATOR)
237 lastState = sc.state;
238 sc.SetState(SCE_CSS_OPERATOR);
239 op = sc.ch;
240 }
241 }
242
243 sc.Complete();
244 }
245
FoldCSSDoc(unsigned int startPos,int length,int,WordList * [],Accessor & styler)246 static void FoldCSSDoc(unsigned int startPos, int length, int, WordList *[], Accessor &styler) {
247 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
248 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
249 unsigned int endPos = startPos + length;
250 int visibleChars = 0;
251 int lineCurrent = styler.GetLine(startPos);
252 int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
253 int levelCurrent = levelPrev;
254 char chNext = styler[startPos];
255 bool inComment = (styler.StyleAt(startPos-1) == SCE_CSS_COMMENT);
256 for (unsigned int i = startPos; i < endPos; i++) {
257 char ch = chNext;
258 chNext = styler.SafeGetCharAt(i + 1);
259 int style = styler.StyleAt(i);
260 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
261 if (foldComment) {
262 if (!inComment && (style == SCE_CSS_COMMENT))
263 levelCurrent++;
264 else if (inComment && (style != SCE_CSS_COMMENT))
265 levelCurrent--;
266 inComment = (style == SCE_CSS_COMMENT);
267 }
268 if (style == SCE_CSS_OPERATOR) {
269 if (ch == '{') {
270 levelCurrent++;
271 } else if (ch == '}') {
272 levelCurrent--;
273 }
274 }
275 if (atEOL) {
276 int lev = levelPrev;
277 if (visibleChars == 0 && foldCompact)
278 lev |= SC_FOLDLEVELWHITEFLAG;
279 if ((levelCurrent > levelPrev) && (visibleChars > 0))
280 lev |= SC_FOLDLEVELHEADERFLAG;
281 if (lev != styler.LevelAt(lineCurrent)) {
282 styler.SetLevel(lineCurrent, lev);
283 }
284 lineCurrent++;
285 levelPrev = levelCurrent;
286 visibleChars = 0;
287 }
288 if (!isspacechar(ch))
289 visibleChars++;
290 }
291 // Fill in the real level of the next line, keeping the current flags as they will be filled in later
292 int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
293 styler.SetLevel(lineCurrent, levelPrev | flagsNext);
294 }
295
296 static const char * const cssWordListDesc[] = {
297 "CSS1 Keywords",
298 "Pseudo classes",
299 "CSS2 Keywords",
300 0
301 };
302
303 LexerModule lmCss(SCLEX_CSS, ColouriseCssDoc, "css", FoldCSSDoc, cssWordListDesc);
304