1 // Scintilla source code edit control
3 ** Lexer for C++, C, Java, and JavaScript.
5 // Copyright 1998-2005 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(int ch
) {
27 return (ch
== '(') || (ch
== '=') || (ch
== ',');
30 static inline bool IsAWordChar(int ch
) {
31 return (ch
< 0x80) && (isalnum(ch
) || ch
== '.' || ch
== '_');
34 static inline bool IsAWordStart(int ch
) {
35 return (ch
< 0x80) && (isalpha(ch
) || ch
== '_');
38 static inline bool IsADoxygenChar(int ch
) {
39 return (ch
< 0x80 && islower(ch
)) || ch
== '$' || ch
== '@' ||
40 ch
== '\\' || ch
== '&' || ch
== '<' ||
41 ch
== '>' || ch
== '#' || ch
== '{' ||
42 ch
== '}' || ch
== '[' || ch
== ']';
45 static bool IsSpaceEquiv(int state
) {
46 return (state
<= SCE_C_COMMENTDOC
) ||
47 // including SCE_C_DEFAULT, SCE_C_COMMENT, SCE_C_COMMENTLINE
48 (state
== SCE_C_COMMENTLINEDOC
) || (state
== SCE_C_COMMENTDOCKEYWORD
) ||
49 (state
== SCE_C_COMMENTDOCKEYWORDERROR
);
52 static void ColouriseCppDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
53 Accessor
&styler
, bool caseSensitive
) {
55 WordList
&keywords
= *keywordlists
[0];
56 WordList
&keywords2
= *keywordlists
[1];
57 WordList
&keywords3
= *keywordlists
[2];
58 WordList
&keywords4
= *keywordlists
[3];
60 bool stylingWithinPreprocessor
= styler
.GetPropertyInt("styling.within.preprocessor") != 0;
62 int chPrevNonWhite
= ' ';
64 bool lastWordWasUUID
= false;
65 int styleBeforeDCKeyword
= SCE_C_DEFAULT
;
66 bool continuationLine
= false;
68 if (initStyle
== SCE_C_PREPROCESSOR
) {
69 // Set continuationLine if last character of previous line is '\'
70 int lineCurrent
= styler
.GetLine(startPos
);
71 if (lineCurrent
> 0) {
72 int chBack
= styler
.SafeGetCharAt(startPos
-1, 0);
73 int chBack2
= styler
.SafeGetCharAt(startPos
-2, 0);
74 int lineEndChar
= '!';
75 if (chBack2
== '\r' && chBack
== '\n') {
76 lineEndChar
= styler
.SafeGetCharAt(startPos
-3, 0);
77 } else if (chBack
== '\n' || chBack
== '\r') {
78 lineEndChar
= chBack2
;
80 continuationLine
= lineEndChar
== '\\';
84 // look back to set chPrevNonWhite properly for better regex colouring
87 while (--back
&& IsSpaceEquiv(styler
.StyleAt(back
)))
89 if (styler
.StyleAt(back
) == SCE_C_OPERATOR
) {
90 chPrevNonWhite
= styler
.SafeGetCharAt(back
);
94 StyleContext
sc(startPos
, length
, initStyle
, styler
);
96 for (; sc
.More(); sc
.Forward()) {
99 if (sc
.state
== SCE_C_STRING
) {
100 // Prevent SCE_C_STRINGEOL from leaking back to previous line which
101 // ends with a line continuation by locking in the state upto this position.
102 sc
.SetState(SCE_C_STRING
);
104 // Reset states to begining of colourise so no surprises
105 // if different sets of lines lexed.
107 lastWordWasUUID
= false;
110 // Handle line continuation generically.
112 if (sc
.chNext
== '\n' || sc
.chNext
== '\r') {
114 if (sc
.ch
== '\r' && sc
.chNext
== '\n') {
117 continuationLine
= true;
122 // Determine if the current state should terminate.
125 sc
.SetState(SCE_C_DEFAULT
);
128 // We accept almost anything because of hex. and number suffixes
129 if (!IsAWordChar(sc
.ch
)) {
130 sc
.SetState(SCE_C_DEFAULT
);
133 case SCE_C_IDENTIFIER
:
134 if (!IsAWordChar(sc
.ch
) || (sc
.ch
== '.')) {
137 sc
.GetCurrent(s
, sizeof(s
));
139 sc
.GetCurrentLowered(s
, sizeof(s
));
141 if (keywords
.InList(s
)) {
142 lastWordWasUUID
= strcmp(s
, "uuid") == 0;
143 sc
.ChangeState(SCE_C_WORD
);
144 } else if (keywords2
.InList(s
)) {
145 sc
.ChangeState(SCE_C_WORD2
);
146 } else if (keywords4
.InList(s
)) {
147 sc
.ChangeState(SCE_C_GLOBALCLASS
);
149 sc
.SetState(SCE_C_DEFAULT
);
152 case SCE_C_PREPROCESSOR
:
153 if (sc
.atLineStart
&& !continuationLine
) {
154 sc
.SetState(SCE_C_DEFAULT
);
155 } else if (stylingWithinPreprocessor
) {
156 if (IsASpace(sc
.ch
)) {
157 sc
.SetState(SCE_C_DEFAULT
);
160 if (sc
.Match('/', '*') || sc
.Match('/', '/')) {
161 sc
.SetState(SCE_C_DEFAULT
);
166 if (sc
.Match('*', '/')) {
168 sc
.ForwardSetState(SCE_C_DEFAULT
);
171 case SCE_C_COMMENTDOC
:
172 if (sc
.Match('*', '/')) {
174 sc
.ForwardSetState(SCE_C_DEFAULT
);
175 } else if (sc
.ch
== '@' || sc
.ch
== '\\') { // JavaDoc and Doxygen support
176 // Verify that we have the conditions to mark a comment-doc-keyword
177 if ((IsASpace(sc
.chPrev
) || sc
.chPrev
== '*') && (!IsASpace(sc
.chNext
))) {
178 styleBeforeDCKeyword
= SCE_C_COMMENTDOC
;
179 sc
.SetState(SCE_C_COMMENTDOCKEYWORD
);
183 case SCE_C_COMMENTLINE
:
184 if (sc
.atLineStart
) {
185 sc
.SetState(SCE_C_DEFAULT
);
188 case SCE_C_COMMENTLINEDOC
:
189 if (sc
.atLineStart
) {
190 sc
.SetState(SCE_C_DEFAULT
);
191 } else if (sc
.ch
== '@' || sc
.ch
== '\\') { // JavaDoc and Doxygen support
192 // Verify that we have the conditions to mark a comment-doc-keyword
193 if ((IsASpace(sc
.chPrev
) || sc
.chPrev
== '/' || sc
.chPrev
== '!') && (!IsASpace(sc
.chNext
))) {
194 styleBeforeDCKeyword
= SCE_C_COMMENTLINEDOC
;
195 sc
.SetState(SCE_C_COMMENTDOCKEYWORD
);
199 case SCE_C_COMMENTDOCKEYWORD
:
200 if ((styleBeforeDCKeyword
== SCE_C_COMMENTDOC
) && sc
.Match('*', '/')) {
201 sc
.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR
);
203 sc
.ForwardSetState(SCE_C_DEFAULT
);
204 } else if (!IsADoxygenChar(sc
.ch
)) {
207 sc
.GetCurrent(s
, sizeof(s
));
209 sc
.GetCurrentLowered(s
, sizeof(s
));
211 if (!isspace(sc
.ch
) || !keywords3
.InList(s
+ 1)) {
212 sc
.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR
);
214 sc
.SetState(styleBeforeDCKeyword
);
219 sc
.ChangeState(SCE_C_STRINGEOL
);
220 } else if (sc
.ch
== '\\') {
221 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
224 } else if (sc
.ch
== '\"') {
225 sc
.ForwardSetState(SCE_C_DEFAULT
);
228 case SCE_C_CHARACTER
:
230 sc
.ChangeState(SCE_C_STRINGEOL
);
231 } else if (sc
.ch
== '\\') {
232 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
235 } else if (sc
.ch
== '\'') {
236 sc
.ForwardSetState(SCE_C_DEFAULT
);
240 if (sc
.atLineStart
) {
241 sc
.SetState(SCE_C_DEFAULT
);
242 } else if (sc
.ch
== '/') {
244 while ((sc
.ch
< 0x80) && islower(sc
.ch
))
245 sc
.Forward(); // gobble regex flags
246 sc
.SetState(SCE_C_DEFAULT
);
247 } else if (sc
.ch
== '\\') {
248 // Gobble up the quoted character
249 if (sc
.chNext
== '\\' || sc
.chNext
== '/') {
254 case SCE_C_STRINGEOL
:
255 if (sc
.atLineStart
) {
256 sc
.SetState(SCE_C_DEFAULT
);
261 if (sc
.chNext
== '\"') {
264 sc
.ForwardSetState(SCE_C_DEFAULT
);
269 if (sc
.ch
== '\r' || sc
.ch
== '\n' || sc
.ch
== ')') {
270 sc
.SetState(SCE_C_DEFAULT
);
274 // Determine if a new state should be entered.
275 if (sc
.state
== SCE_C_DEFAULT
) {
276 if (sc
.Match('@', '\"')) {
277 sc
.SetState(SCE_C_VERBATIM
);
279 } else if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
))) {
280 if (lastWordWasUUID
) {
281 sc
.SetState(SCE_C_UUID
);
282 lastWordWasUUID
= false;
284 sc
.SetState(SCE_C_NUMBER
);
286 } else if (IsAWordStart(sc
.ch
) || (sc
.ch
== '@')) {
287 if (lastWordWasUUID
) {
288 sc
.SetState(SCE_C_UUID
);
289 lastWordWasUUID
= false;
291 sc
.SetState(SCE_C_IDENTIFIER
);
293 } else if (sc
.Match('/', '*')) {
294 if (sc
.Match("/**") || sc
.Match("/*!")) { // Support of Qt/Doxygen doc. style
295 sc
.SetState(SCE_C_COMMENTDOC
);
297 sc
.SetState(SCE_C_COMMENT
);
299 sc
.Forward(); // Eat the * so it isn't used for the end of the comment
300 } else if (sc
.Match('/', '/')) {
301 if ((sc
.Match("///") && !sc
.Match("////")) || sc
.Match("//!"))
302 // Support of Qt/Doxygen doc. style
303 sc
.SetState(SCE_C_COMMENTLINEDOC
);
305 sc
.SetState(SCE_C_COMMENTLINE
);
306 } else if (sc
.ch
== '/' && IsOKBeforeRE(chPrevNonWhite
)) {
307 sc
.SetState(SCE_C_REGEX
); // JavaScript's RegEx
308 } else if (sc
.ch
== '\"') {
309 sc
.SetState(SCE_C_STRING
);
310 } else if (sc
.ch
== '\'') {
311 sc
.SetState(SCE_C_CHARACTER
);
312 } else if (sc
.ch
== '#' && visibleChars
== 0) {
313 // Preprocessor commands are alone on their line
314 sc
.SetState(SCE_C_PREPROCESSOR
);
315 // Skip whitespace between # and preprocessor word
318 } while ((sc
.ch
== ' ' || sc
.ch
== '\t') && sc
.More());
320 sc
.SetState(SCE_C_DEFAULT
);
322 } else if (isoperator(static_cast<char>(sc
.ch
))) {
323 sc
.SetState(SCE_C_OPERATOR
);
327 if (!IsASpace(sc
.ch
) && !IsSpaceEquiv(sc
.state
)) {
328 chPrevNonWhite
= sc
.ch
;
331 continuationLine
= false;
336 static bool IsStreamCommentStyle(int style
) {
337 return style
== SCE_C_COMMENT
||
338 style
== SCE_C_COMMENTDOC
||
339 style
== SCE_C_COMMENTDOCKEYWORD
||
340 style
== SCE_C_COMMENTDOCKEYWORDERROR
;
343 // Store both the current line's fold level and the next lines in the
344 // level store to make it easy to pick up with each increment
345 // and to make it possible to fiddle the current level for "} else {".
346 static void FoldNoBoxCppDoc(unsigned int startPos
, int length
, int initStyle
,
348 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
349 bool foldPreprocessor
= styler
.GetPropertyInt("fold.preprocessor") != 0;
350 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
351 bool foldAtElse
= styler
.GetPropertyInt("fold.at.else", 0) != 0;
352 unsigned int endPos
= startPos
+ length
;
353 int visibleChars
= 0;
354 int lineCurrent
= styler
.GetLine(startPos
);
355 int levelCurrent
= SC_FOLDLEVELBASE
;
357 levelCurrent
= styler
.LevelAt(lineCurrent
-1) >> 16;
358 int levelMinCurrent
= levelCurrent
;
359 int levelNext
= levelCurrent
;
360 char chNext
= styler
[startPos
];
361 int styleNext
= styler
.StyleAt(startPos
);
362 int style
= initStyle
;
363 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
365 chNext
= styler
.SafeGetCharAt(i
+ 1);
366 int stylePrev
= style
;
368 styleNext
= styler
.StyleAt(i
+ 1);
369 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
370 if (foldComment
&& IsStreamCommentStyle(style
)) {
371 if (!IsStreamCommentStyle(stylePrev
)) {
373 } else if (!IsStreamCommentStyle(styleNext
) && !atEOL
) {
374 // Comments don't end at end of line and the next character may be unstyled.
378 if (foldComment
&& (style
== SCE_C_COMMENTLINE
)) {
379 if ((ch
== '/') && (chNext
== '/')) {
380 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
381 if (chNext2
== '{') {
383 } else if (chNext2
== '}') {
388 if (foldPreprocessor
&& (style
== SCE_C_PREPROCESSOR
)) {
390 unsigned int j
= i
+ 1;
391 while ((j
< endPos
) && IsASpaceOrTab(styler
.SafeGetCharAt(j
))) {
394 if (styler
.Match(j
, "region") || styler
.Match(j
, "if")) {
396 } else if (styler
.Match(j
, "end")) {
401 if (style
== SCE_C_OPERATOR
) {
403 // Measure the minimum before a '{' to allow
404 // folding on "} else {"
405 if (levelMinCurrent
> levelNext
) {
406 levelMinCurrent
= levelNext
;
409 } else if (ch
== '}') {
414 int levelUse
= levelCurrent
;
416 levelUse
= levelMinCurrent
;
418 int lev
= levelUse
| levelNext
<< 16;
419 if (visibleChars
== 0 && foldCompact
)
420 lev
|= SC_FOLDLEVELWHITEFLAG
;
421 if (levelUse
< levelNext
)
422 lev
|= SC_FOLDLEVELHEADERFLAG
;
423 if (lev
!= styler
.LevelAt(lineCurrent
)) {
424 styler
.SetLevel(lineCurrent
, lev
);
427 levelCurrent
= levelNext
;
428 levelMinCurrent
= levelCurrent
;
431 if (!isspacechar(ch
))
436 static void FoldCppDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*[],
438 FoldNoBoxCppDoc(startPos
, length
, initStyle
, styler
);
441 static const char * const cppWordLists
[] = {
442 "Primary keywords and identifiers",
443 "Secondary keywords and identifiers",
444 "Documentation comment keywords",
446 "Global classes and typedefs",
450 static void ColouriseCppDocSensitive(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
452 ColouriseCppDoc(startPos
, length
, initStyle
, keywordlists
, styler
, true);
455 static void ColouriseCppDocInsensitive(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
457 ColouriseCppDoc(startPos
, length
, initStyle
, keywordlists
, styler
, false);
460 LexerModule
lmCPP(SCLEX_CPP
, ColouriseCppDocSensitive
, "cpp", FoldCppDoc
, cppWordLists
);
461 LexerModule
lmCPPNoCase(SCLEX_CPPNOCASE
, ColouriseCppDocInsensitive
, "cppnocase", FoldCppDoc
, cppWordLists
);
462 LexerModule
lmTCL(SCLEX_TCL
, ColouriseCppDocSensitive
, "tcl", FoldCppDoc
, cppWordLists
);