1 // Scintilla source code edit control
3 ** Lexer for C++, C, Java, and Javascript.
5 // Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
18 #include "StyleContext.h"
20 #include "Scintilla.h"
23 #define KEYWORD_BOXHEADER 1
24 #define KEYWORD_FOLDCONTRACTED 2
26 static bool IsOKBeforeRE(const int ch
) {
27 return (ch
== '(') || (ch
== '=') || (ch
== ',');
30 static inline bool IsAWordChar(const int ch
) {
31 return (ch
< 0x80) && (isalnum(ch
) || ch
== '.' || ch
== '_');
34 static inline bool IsAWordStart(const int ch
) {
35 return (ch
< 0x80) && (isalnum(ch
) || ch
== '_');
38 static inline bool IsADoxygenChar(const int ch
) {
39 return (islower(ch
) || ch
== '$' || ch
== '@' ||
40 ch
== '\\' || ch
== '&' || ch
== '<' ||
41 ch
== '>' || ch
== '#' || ch
== '{' ||
42 ch
== '}' || ch
== '[' || ch
== ']');
45 static inline bool IsStateComment(const int state
) {
46 return ((state
== SCE_C_COMMENT
) ||
47 (state
== SCE_C_COMMENTLINE
) ||
48 (state
== SCE_C_COMMENTDOC
) ||
49 (state
== SCE_C_COMMENTDOCKEYWORD
) ||
50 (state
== SCE_C_COMMENTDOCKEYWORDERROR
));
53 static inline bool IsStateString(const int state
) {
54 return ((state
== SCE_C_STRING
) || (state
== SCE_C_VERBATIM
));
57 static void ColouriseCppDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
58 Accessor
&styler
, bool caseSensitive
) {
60 WordList
&keywords
= *keywordlists
[0];
61 WordList
&keywords2
= *keywordlists
[1];
62 WordList
&keywords3
= *keywordlists
[2];
63 WordList
&keywords4
= *keywordlists
[3];
65 bool stylingWithinPreprocessor
= styler
.GetPropertyInt("styling.within.preprocessor") != 0;
67 // Do not leak onto next line
68 if (initStyle
== SCE_C_STRINGEOL
)
69 initStyle
= SCE_C_DEFAULT
;
71 int chPrevNonWhite
= ' ';
73 bool lastWordWasUUID
= false;
75 StyleContext
sc(startPos
, length
, initStyle
, styler
);
77 for (; sc
.More(); sc
.Forward()) {
79 if (sc
.atLineStart
&& (sc
.state
== SCE_C_STRING
)) {
80 // Prevent SCE_C_STRINGEOL from leaking back to previous line
81 sc
.SetState(SCE_C_STRING
);
84 // Handle line continuation generically.
86 if (sc
.chNext
== '\n' || sc
.chNext
== '\r') {
88 if (sc
.ch
== '\r' && sc
.chNext
== '\n') {
95 // Determine if the current state should terminate.
96 if (sc
.state
== SCE_C_OPERATOR
) {
97 sc
.SetState(SCE_C_DEFAULT
);
98 } else if (sc
.state
== SCE_C_NUMBER
) {
99 if (!IsAWordChar(sc
.ch
)) {
100 sc
.SetState(SCE_C_DEFAULT
);
102 } else if (sc
.state
== SCE_C_IDENTIFIER
) {
103 if (!IsAWordChar(sc
.ch
) || (sc
.ch
== '.')) {
106 sc
.GetCurrent(s
, sizeof(s
));
108 sc
.GetCurrentLowered(s
, sizeof(s
));
110 if (keywords
.InList(s
)) {
111 lastWordWasUUID
= strcmp(s
, "uuid") == 0;
112 sc
.ChangeState(SCE_C_WORD
);
113 } else if (keywords2
.InList(s
)) {
114 sc
.ChangeState(SCE_C_WORD2
);
115 } else if (keywords4
.InList(s
)) {
116 sc
.ChangeState(SCE_C_GLOBALCLASS
);
118 sc
.SetState(SCE_C_DEFAULT
);
120 } else if (sc
.state
== SCE_C_PREPROCESSOR
) {
121 if (stylingWithinPreprocessor
) {
122 if (IsASpace(sc
.ch
)) {
123 sc
.SetState(SCE_C_DEFAULT
);
126 if ((sc
.ch
== '\r') || (sc
.ch
== '\n') || (sc
.Match('/', '*')) || (sc
.Match('/', '/'))) {
127 sc
.SetState(SCE_C_DEFAULT
);
130 } else if (sc
.state
== SCE_C_COMMENT
) {
131 if (sc
.Match('*', '/')) {
133 sc
.ForwardSetState(SCE_C_DEFAULT
);
135 } else if (sc
.state
== SCE_C_COMMENTDOC
) {
136 if (sc
.Match('*', '/')) {
138 sc
.ForwardSetState(SCE_C_DEFAULT
);
139 } else if (sc
.ch
== '@' || sc
.ch
== '\\') {
140 sc
.SetState(SCE_C_COMMENTDOCKEYWORD
);
142 } else if (sc
.state
== SCE_C_COMMENTLINE
|| sc
.state
== SCE_C_COMMENTLINEDOC
) {
143 if (sc
.ch
== '\r' || sc
.ch
== '\n') {
144 sc
.SetState(SCE_C_DEFAULT
);
147 } else if (sc
.state
== SCE_C_COMMENTDOCKEYWORD
) {
148 if (sc
.Match('*', '/')) {
149 sc
.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR
);
151 sc
.ForwardSetState(SCE_C_DEFAULT
);
152 } else if (!IsADoxygenChar(sc
.ch
)) {
155 sc
.GetCurrent(s
, sizeof(s
));
157 sc
.GetCurrentLowered(s
, sizeof(s
));
159 if (!isspace(sc
.ch
) || !keywords3
.InList(s
+ 1)) {
160 sc
.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR
);
162 sc
.SetState(SCE_C_COMMENTDOC
);
164 } else if (sc
.state
== SCE_C_STRING
) {
166 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
169 } else if (sc
.ch
== '\"') {
170 sc
.ForwardSetState(SCE_C_DEFAULT
);
171 } else if (sc
.atLineEnd
) {
172 sc
.ChangeState(SCE_C_STRINGEOL
);
173 sc
.ForwardSetState(SCE_C_DEFAULT
);
176 } else if (sc
.state
== SCE_C_CHARACTER
) {
178 sc
.ChangeState(SCE_C_STRINGEOL
);
179 sc
.ForwardSetState(SCE_C_DEFAULT
);
181 } else if (sc
.ch
== '\\') {
182 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
185 } else if (sc
.ch
== '\'') {
186 sc
.ForwardSetState(SCE_C_DEFAULT
);
188 } else if (sc
.state
== SCE_C_REGEX
) {
189 if (sc
.ch
== '\r' || sc
.ch
== '\n' || sc
.ch
== '/') {
190 sc
.ForwardSetState(SCE_C_DEFAULT
);
191 } else if (sc
.ch
== '\\') {
192 // Gobble up the quoted character
193 if (sc
.chNext
== '\\' || sc
.chNext
== '/') {
197 } else if (sc
.state
== SCE_C_VERBATIM
) {
199 if (sc
.chNext
== '\"') {
202 sc
.ForwardSetState(SCE_C_DEFAULT
);
205 } else if (sc
.state
== SCE_C_UUID
) {
206 if (sc
.ch
== '\r' || sc
.ch
== '\n' || sc
.ch
== ')') {
207 sc
.SetState(SCE_C_DEFAULT
);
211 // Determine if a new state should be entered.
212 if (sc
.state
== SCE_C_DEFAULT
) {
213 if (sc
.Match('@', '\"')) {
214 sc
.SetState(SCE_C_VERBATIM
);
216 } else if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
))) {
217 if (lastWordWasUUID
) {
218 sc
.SetState(SCE_C_UUID
);
219 lastWordWasUUID
= false;
221 sc
.SetState(SCE_C_NUMBER
);
223 } else if (IsAWordStart(sc
.ch
) || (sc
.ch
== '@')) {
224 if (lastWordWasUUID
) {
225 sc
.SetState(SCE_C_UUID
);
226 lastWordWasUUID
= false;
228 sc
.SetState(SCE_C_IDENTIFIER
);
230 } else if (sc
.Match('/', '*')) {
231 if (sc
.Match("/**") || sc
.Match("/*!")) { // Support of Qt/Doxygen doc. style
232 sc
.SetState(SCE_C_COMMENTDOC
);
234 sc
.SetState(SCE_C_COMMENT
);
236 sc
.Forward(); // Eat the * so it isn't used for the end of the comment
237 } else if (sc
.Match('/', '/')) {
238 if ((sc
.Match("///") && !sc
.Match("////")) || sc
.Match("//!"))
239 // Support of Qt/Doxygen doc. style
240 sc
.SetState(SCE_C_COMMENTLINEDOC
);
242 sc
.SetState(SCE_C_COMMENTLINE
);
243 } else if (sc
.ch
== '/' && IsOKBeforeRE(chPrevNonWhite
)) {
244 sc
.SetState(SCE_C_REGEX
);
245 } else if (sc
.ch
== '\"') {
246 sc
.SetState(SCE_C_STRING
);
247 } else if (sc
.ch
== '\'') {
248 sc
.SetState(SCE_C_CHARACTER
);
249 } else if (sc
.ch
== '#' && visibleChars
== 0) {
250 // Preprocessor commands are alone on their line
251 sc
.SetState(SCE_C_PREPROCESSOR
);
252 // Skip whitespace between # and preprocessor word
255 } while ((sc
.ch
== ' ' || sc
.ch
== '\t') && sc
.More());
256 if (sc
.ch
== '\r' || sc
.ch
== '\n') {
257 sc
.SetState(SCE_C_DEFAULT
);
259 } else if (isoperator(static_cast<char>(sc
.ch
))) {
260 sc
.SetState(SCE_C_OPERATOR
);
265 // Reset states to begining of colourise so no surprises
266 // if different sets of lines lexed.
267 chPrevNonWhite
= ' ';
269 lastWordWasUUID
= false;
271 if (!IsASpace(sc
.ch
)) {
272 chPrevNonWhite
= sc
.ch
;
279 static bool IsStreamCommentStyle(int style
) {
280 return style
== SCE_C_COMMENT
||
281 style
== SCE_C_COMMENTDOC
||
282 style
== SCE_C_COMMENTDOCKEYWORD
||
283 style
== SCE_C_COMMENTDOCKEYWORDERROR
;
286 // Store both the current line's fold level and the next lines in the
287 // level store to make it easy to pick up with each increment
288 // and to make it possible to fiddle the current level for "} else {".
289 static void FoldNoBoxCppDoc(unsigned int startPos
, int length
, int initStyle
,
291 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
292 bool foldPreprocessor
= styler
.GetPropertyInt("fold.preprocessor") != 0;
293 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
294 bool foldAtElse
= styler
.GetPropertyInt("fold.at.else", 0) != 0;
295 unsigned int endPos
= startPos
+ length
;
296 int visibleChars
= 0;
297 int lineCurrent
= styler
.GetLine(startPos
);
298 int levelCurrent
= SC_FOLDLEVELBASE
;
300 levelCurrent
= styler
.LevelAt(lineCurrent
-1) >> 16;
301 int levelMinCurrent
= levelCurrent
;
302 int levelNext
= levelCurrent
;
303 char chNext
= styler
[startPos
];
304 int styleNext
= styler
.StyleAt(startPos
);
305 int style
= initStyle
;
306 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
308 chNext
= styler
.SafeGetCharAt(i
+ 1);
309 int stylePrev
= style
;
311 styleNext
= styler
.StyleAt(i
+ 1);
312 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
313 if (foldComment
&& IsStreamCommentStyle(style
)) {
314 if (!IsStreamCommentStyle(stylePrev
)) {
316 } else if (!IsStreamCommentStyle(styleNext
) && !atEOL
) {
317 // Comments don't end at end of line and the next character may be unstyled.
321 if (foldComment
&& (style
== SCE_C_COMMENTLINE
)) {
322 if ((ch
== '/') && (chNext
== '/')) {
323 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
324 if (chNext2
== '{') {
326 } else if (chNext2
== '}') {
331 if (foldPreprocessor
&& (style
== SCE_C_PREPROCESSOR
)) {
333 unsigned int j
= i
+ 1;
334 while ((j
< endPos
) && IsASpaceOrTab(styler
.SafeGetCharAt(j
))) {
337 if (styler
.Match(j
, "region") || styler
.Match(j
, "if")) {
339 } else if (styler
.Match(j
, "end")) {
344 if (style
== SCE_C_OPERATOR
) {
346 // Measure the minimum before a '{' to allow
347 // folding on "} else {"
348 if (levelMinCurrent
> levelNext
) {
349 levelMinCurrent
= levelNext
;
352 } else if (ch
== '}') {
357 int levelUse
= levelCurrent
;
359 levelUse
= levelMinCurrent
;
361 int lev
= levelUse
| levelNext
<< 16;
362 if (visibleChars
== 0 && foldCompact
)
363 lev
|= SC_FOLDLEVELWHITEFLAG
;
364 if (levelUse
< levelNext
)
365 lev
|= SC_FOLDLEVELHEADERFLAG
;
366 if (lev
!= styler
.LevelAt(lineCurrent
)) {
367 styler
.SetLevel(lineCurrent
, lev
);
370 levelCurrent
= levelNext
;
371 levelMinCurrent
= levelCurrent
;
374 if (!isspacechar(ch
))
379 static void FoldCppDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*[],
381 FoldNoBoxCppDoc(startPos
, length
, initStyle
, styler
);
384 static const char * const cppWordLists
[] = {
385 "Primary keywords and identifiers",
386 "Secondary keywords and identifiers",
387 "Documentation comment keywords",
389 "Global classes and typedefs",
393 static void ColouriseCppDocSensitive(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
395 ColouriseCppDoc(startPos
, length
, initStyle
, keywordlists
, styler
, true);
398 static void ColouriseCppDocInsensitive(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
400 ColouriseCppDoc(startPos
, length
, initStyle
, keywordlists
, styler
, false);
403 LexerModule
lmCPP(SCLEX_CPP
, ColouriseCppDocSensitive
, "cpp", FoldCppDoc
, cppWordLists
);
404 LexerModule
lmCPPNoCase(SCLEX_CPPNOCASE
, ColouriseCppDocInsensitive
, "cppnocase", FoldCppDoc
, cppWordLists
);
405 LexerModule
lmTCL(SCLEX_TCL
, ColouriseCppDocSensitive
, "tcl", FoldCppDoc
, cppWordLists
);