1 // Scintilla source code edit control
\r
3 ** Lexer for Cascading Style Sheets
\r
4 ** Written by Jakub Vrána
\r
5 ** Improved by Philippe Lhoste (CSS2)
\r
7 // Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
\r
8 // The License.txt file describes the conditions under which this software may be distributed.
\r
16 #include "Platform.h"
\r
18 #include "PropSet.h"
\r
19 #include "Accessor.h"
\r
20 #include "StyleContext.h"
\r
21 #include "KeyWords.h"
\r
22 #include "Scintilla.h"
\r
23 #include "SciLexer.h"
\r
25 #ifdef SCI_NAMESPACE
\r
26 using namespace Scintilla;
\r
30 static inline bool IsAWordChar(const unsigned int ch) {
\r
32 * The CSS spec allows "ISO 10646 characters U+00A1 and higher" to be treated as word chars.
\r
33 * Unfortunately, we are only getting string bytes here, and not full unicode characters. We cannot guarantee
\r
34 * that our byte is between U+0080 - U+00A0 (to return false), so we have to allow all characters U+0080 and higher
\r
36 return ch >= 0x80 || isalnum(ch) || ch == '-' || ch == '_';
\r
39 inline bool IsCssOperator(const int ch) {
\r
40 if (!((ch < 0x80) && isalnum(ch)) &&
\r
41 (ch == '{' || ch == '}' || ch == ':' || ch == ',' || ch == ';' ||
\r
42 ch == '.' || ch == '#' || ch == '!' || ch == '@' ||
\r
44 ch == '*' || ch == '>' || ch == '+' || ch == '=' || ch == '~' || ch == '|' ||
\r
45 ch == '[' || ch == ']' || ch == '(' || ch == ')')) {
\r
51 static void ColouriseCssDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[], Accessor &styler) {
\r
52 WordList &css1Props = *keywordlists[0];
\r
53 WordList &pseudoClasses = *keywordlists[1];
\r
54 WordList &css2Props = *keywordlists[2];
\r
55 WordList &css3Props = *keywordlists[3];
\r
56 WordList &pseudoElements = *keywordlists[4];
\r
57 WordList &exProps = *keywordlists[5];
\r
58 WordList &exPseudoClasses = *keywordlists[6];
\r
59 WordList &exPseudoElements = *keywordlists[7];
\r
61 StyleContext sc(startPos, length, initStyle, styler);
\r
63 int lastState = -1; // before operator
\r
64 int lastStateC = -1; // before comment
\r
65 int op = ' '; // last operator
\r
66 int opPrev = ' '; // last operator
\r
68 for (; sc.More(); sc.Forward()) {
\r
69 if (sc.state == SCE_CSS_COMMENT && sc.Match('*', '/')) {
\r
70 if (lastStateC == -1) {
\r
71 // backtrack to get last state:
\r
72 // comments are like whitespace, so we must return to the previous state
\r
73 unsigned int i = startPos;
\r
74 for (; i > 0; i--) {
\r
75 if ((lastStateC = styler.StyleAt(i-1)) != SCE_CSS_COMMENT) {
\r
76 if (lastStateC == SCE_CSS_OPERATOR) {
\r
77 op = styler.SafeGetCharAt(i-1);
\r
78 opPrev = styler.SafeGetCharAt(i-2);
\r
80 lastState = styler.StyleAt(i-1);
\r
81 if (lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT)
\r
85 lastState = SCE_CSS_DEFAULT;
\r
91 lastStateC = SCE_CSS_DEFAULT;
\r
94 sc.ForwardSetState(lastStateC);
\r
97 if (sc.state == SCE_CSS_COMMENT)
\r
100 if (sc.state == SCE_CSS_DOUBLESTRING || sc.state == SCE_CSS_SINGLESTRING) {
\r
101 if (sc.ch != (sc.state == SCE_CSS_DOUBLESTRING ? '\"' : '\''))
\r
103 unsigned int i = sc.currentPos;
\r
104 while (i && styler[i-1] == '\\')
\r
106 if ((sc.currentPos - i) % 2 == 1)
\r
108 sc.ForwardSetState(SCE_CSS_VALUE);
\r
111 if (sc.state == SCE_CSS_OPERATOR) {
\r
113 unsigned int i = startPos;
\r
114 op = styler.SafeGetCharAt(i-1);
\r
115 opPrev = styler.SafeGetCharAt(i-2);
\r
117 lastState = styler.StyleAt(i-1);
\r
118 if (lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT)
\r
124 if (lastState == SCE_CSS_DEFAULT)
\r
125 sc.SetState(SCE_CSS_DIRECTIVE);
\r
129 if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
\r
130 lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
\r
131 sc.SetState(SCE_CSS_DEFAULT);
\r
134 if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
\r
135 lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
\r
136 sc.SetState(SCE_CSS_ATTRIBUTE);
\r
139 if (lastState == SCE_CSS_ATTRIBUTE)
\r
140 sc.SetState(SCE_CSS_TAG);
\r
143 if (lastState == SCE_CSS_DIRECTIVE)
\r
144 sc.SetState(SCE_CSS_DEFAULT);
\r
145 else if (lastState == SCE_CSS_TAG)
\r
146 sc.SetState(SCE_CSS_IDENTIFIER);
\r
149 if (lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_VALUE || lastState == SCE_CSS_IMPORTANT ||
\r
150 lastState == SCE_CSS_IDENTIFIER || lastState == SCE_CSS_IDENTIFIER2 || lastState == SCE_CSS_IDENTIFIER3)
\r
151 sc.SetState(SCE_CSS_DEFAULT);
\r
154 if (lastState == SCE_CSS_PSEUDOCLASS)
\r
155 sc.SetState(SCE_CSS_TAG);
\r
156 else if (lastState == SCE_CSS_EXTENDED_PSEUDOCLASS)
\r
157 sc.SetState(SCE_CSS_EXTENDED_PSEUDOCLASS);
\r
160 if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
\r
161 lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS ||
\r
162 lastState == SCE_CSS_PSEUDOELEMENT || lastState == SCE_CSS_EXTENDED_PSEUDOELEMENT)
\r
163 sc.SetState(SCE_CSS_TAG);
\r
166 if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
\r
167 lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS ||
\r
168 lastState == SCE_CSS_PSEUDOELEMENT || lastState == SCE_CSS_EXTENDED_PSEUDOELEMENT)
\r
169 sc.SetState(SCE_CSS_PSEUDOCLASS);
\r
170 else if (lastState == SCE_CSS_IDENTIFIER || lastState == SCE_CSS_IDENTIFIER2 ||
\r
171 lastState == SCE_CSS_IDENTIFIER3 || lastState == SCE_CSS_EXTENDED_IDENTIFIER ||
\r
172 lastState == SCE_CSS_UNKNOWN_IDENTIFIER)
\r
173 sc.SetState(SCE_CSS_VALUE);
\r
176 if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
\r
177 lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
\r
178 sc.SetState(SCE_CSS_CLASS);
\r
181 if (lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
\r
182 lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS)
\r
183 sc.SetState(SCE_CSS_ID);
\r
188 if (lastState == SCE_CSS_TAG)
\r
189 sc.SetState(SCE_CSS_DEFAULT);
\r
192 if (lastState == SCE_CSS_DIRECTIVE)
\r
193 sc.SetState(SCE_CSS_DEFAULT);
\r
194 else if (lastState == SCE_CSS_VALUE || lastState == SCE_CSS_IMPORTANT)
\r
195 sc.SetState(SCE_CSS_IDENTIFIER);
\r
198 if (lastState == SCE_CSS_VALUE)
\r
199 sc.SetState(SCE_CSS_IMPORTANT);
\r
204 if (IsAWordChar(sc.ch)) {
\r
205 if (sc.state == SCE_CSS_DEFAULT)
\r
206 sc.SetState(SCE_CSS_TAG);
\r
210 if (sc.ch == '*' && sc.state == SCE_CSS_DEFAULT) {
\r
211 sc.SetState(SCE_CSS_TAG);
\r
215 if (IsAWordChar(sc.chPrev) && (
\r
216 sc.state == SCE_CSS_IDENTIFIER || sc.state == SCE_CSS_IDENTIFIER2 ||
\r
217 sc.state == SCE_CSS_IDENTIFIER3 || sc.state == SCE_CSS_EXTENDED_IDENTIFIER ||
\r
218 sc.state == SCE_CSS_UNKNOWN_IDENTIFIER ||
\r
219 sc.state == SCE_CSS_PSEUDOCLASS || sc.state == SCE_CSS_PSEUDOELEMENT ||
\r
220 sc.state == SCE_CSS_EXTENDED_PSEUDOCLASS || sc.state == SCE_CSS_EXTENDED_PSEUDOELEMENT ||
\r
221 sc.state == SCE_CSS_UNKNOWN_PSEUDOCLASS ||
\r
222 sc.state == SCE_CSS_IMPORTANT
\r
225 sc.GetCurrentLowered(s, sizeof(s));
\r
227 while (*s2 && !IsAWordChar(*s2))
\r
229 switch (sc.state) {
\r
230 case SCE_CSS_IDENTIFIER:
\r
231 case SCE_CSS_IDENTIFIER2:
\r
232 case SCE_CSS_IDENTIFIER3:
\r
233 case SCE_CSS_EXTENDED_IDENTIFIER:
\r
234 case SCE_CSS_UNKNOWN_IDENTIFIER:
\r
235 if (css1Props.InList(s2))
\r
236 sc.ChangeState(SCE_CSS_IDENTIFIER);
\r
237 else if (css2Props.InList(s2))
\r
238 sc.ChangeState(SCE_CSS_IDENTIFIER2);
\r
239 else if (css3Props.InList(s2))
\r
240 sc.ChangeState(SCE_CSS_IDENTIFIER3);
\r
241 else if (exProps.InList(s2))
\r
242 sc.ChangeState(SCE_CSS_EXTENDED_IDENTIFIER);
\r
244 sc.ChangeState(SCE_CSS_UNKNOWN_IDENTIFIER);
\r
246 case SCE_CSS_PSEUDOCLASS:
\r
247 case SCE_CSS_PSEUDOELEMENT:
\r
248 case SCE_CSS_EXTENDED_PSEUDOCLASS:
\r
249 case SCE_CSS_EXTENDED_PSEUDOELEMENT:
\r
250 case SCE_CSS_UNKNOWN_PSEUDOCLASS:
\r
251 if (op == ':' && opPrev != ':' && pseudoClasses.InList(s2))
\r
252 sc.ChangeState(SCE_CSS_PSEUDOCLASS);
\r
253 else if (opPrev == ':' && pseudoElements.InList(s2))
\r
254 sc.ChangeState(SCE_CSS_PSEUDOELEMENT);
\r
255 else if ((op == ':' || (op == '(' && lastState == SCE_CSS_EXTENDED_PSEUDOCLASS)) && opPrev != ':' && exPseudoClasses.InList(s2))
\r
256 sc.ChangeState(SCE_CSS_EXTENDED_PSEUDOCLASS);
\r
257 else if (opPrev == ':' && exPseudoElements.InList(s2))
\r
258 sc.ChangeState(SCE_CSS_EXTENDED_PSEUDOELEMENT);
\r
260 sc.ChangeState(SCE_CSS_UNKNOWN_PSEUDOCLASS);
\r
262 case SCE_CSS_IMPORTANT:
\r
263 if (strcmp(s2, "important") != 0)
\r
264 sc.ChangeState(SCE_CSS_VALUE);
\r
269 if (sc.ch != '.' && sc.ch != ':' && sc.ch != '#' && (
\r
270 sc.state == SCE_CSS_CLASS || sc.state == SCE_CSS_ID ||
\r
271 (sc.ch != '(' && sc.ch != ')' && ( /* This line of the condition makes it possible to extend pseudo-classes with parentheses */
\r
272 sc.state == SCE_CSS_PSEUDOCLASS || sc.state == SCE_CSS_PSEUDOELEMENT ||
\r
273 sc.state == SCE_CSS_EXTENDED_PSEUDOCLASS || sc.state == SCE_CSS_EXTENDED_PSEUDOELEMENT ||
\r
274 sc.state == SCE_CSS_UNKNOWN_PSEUDOCLASS
\r
277 sc.SetState(SCE_CSS_TAG);
\r
279 if (sc.Match('/', '*')) {
\r
280 lastStateC = sc.state;
\r
281 sc.SetState(SCE_CSS_COMMENT);
\r
283 } else if (sc.state == SCE_CSS_VALUE && (sc.ch == '\"' || sc.ch == '\'')) {
\r
284 sc.SetState((sc.ch == '\"' ? SCE_CSS_DOUBLESTRING : SCE_CSS_SINGLESTRING));
\r
285 } else if (IsCssOperator(sc.ch)
\r
286 && (sc.state != SCE_CSS_ATTRIBUTE || sc.ch == ']')
\r
287 && (sc.state != SCE_CSS_VALUE || sc.ch == ';' || sc.ch == '}' || sc.ch == '!')
\r
288 && (sc.state != SCE_CSS_DIRECTIVE || sc.ch == ';' || sc.ch == '{')
\r
290 if (sc.state != SCE_CSS_OPERATOR)
\r
291 lastState = sc.state;
\r
292 sc.SetState(SCE_CSS_OPERATOR);
\r
294 opPrev = sc.chPrev;
\r
301 static void FoldCSSDoc(unsigned int startPos, int length, int, WordList *[], Accessor &styler) {
\r
302 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
\r
303 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
\r
304 unsigned int endPos = startPos + length;
\r
305 int visibleChars = 0;
\r
306 int lineCurrent = styler.GetLine(startPos);
\r
307 int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
\r
308 int levelCurrent = levelPrev;
\r
309 char chNext = styler[startPos];
\r
310 bool inComment = (styler.StyleAt(startPos-1) == SCE_CSS_COMMENT);
\r
311 for (unsigned int i = startPos; i < endPos; i++) {
\r
313 chNext = styler.SafeGetCharAt(i + 1);
\r
314 int style = styler.StyleAt(i);
\r
315 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
\r
317 if (!inComment && (style == SCE_CSS_COMMENT))
\r
319 else if (inComment && (style != SCE_CSS_COMMENT))
\r
321 inComment = (style == SCE_CSS_COMMENT);
\r
323 if (style == SCE_CSS_OPERATOR) {
\r
326 } else if (ch == '}') {
\r
331 int lev = levelPrev;
\r
332 if (visibleChars == 0 && foldCompact)
\r
333 lev |= SC_FOLDLEVELWHITEFLAG;
\r
334 if ((levelCurrent > levelPrev) && (visibleChars > 0))
\r
335 lev |= SC_FOLDLEVELHEADERFLAG;
\r
336 if (lev != styler.LevelAt(lineCurrent)) {
\r
337 styler.SetLevel(lineCurrent, lev);
\r
340 levelPrev = levelCurrent;
\r
343 if (!isspacechar(ch))
\r
346 // Fill in the real level of the next line, keeping the current flags as they will be filled in later
\r
347 int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
\r
348 styler.SetLevel(lineCurrent, levelPrev | flagsNext);
\r
351 static const char * const cssWordListDesc[] = {
\r
357 "Browser-Specific CSS Properties",
\r
358 "Browser-Specific Pseudo-classes",
\r
359 "Browser-Specific Pseudo-elements",
\r
363 LexerModule lmCss(SCLEX_CSS, ColouriseCssDoc, "css", FoldCSSDoc, cssWordListDesc);
\r