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