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];
64 bool stylingWithinPreprocessor
= styler
.GetPropertyInt("styling.within.preprocessor") != 0;
66 // Do not leak onto next line
67 if (initStyle
== SCE_C_STRINGEOL
)
68 initStyle
= SCE_C_DEFAULT
;
70 int chPrevNonWhite
= ' ';
72 bool lastWordWasUUID
= false;
74 StyleContext
sc(startPos
, length
, initStyle
, styler
);
76 for (; sc
.More(); sc
.Forward()) {
78 if (sc
.atLineStart
&& (sc
.state
== SCE_C_STRING
)) {
79 // Prevent SCE_C_STRINGEOL from leaking back to previous line
80 sc
.SetState(SCE_C_STRING
);
83 // Handle line continuation generically.
85 if (sc
.chNext
== '\n' || sc
.chNext
== '\r') {
87 if (sc
.ch
== '\r' && sc
.chNext
== '\n') {
94 // Determine if the current state should terminate.
95 if (sc
.state
== SCE_C_OPERATOR
) {
96 sc
.SetState(SCE_C_DEFAULT
);
97 } else if (sc
.state
== SCE_C_NUMBER
) {
98 if (!IsAWordChar(sc
.ch
)) {
99 sc
.SetState(SCE_C_DEFAULT
);
101 } else if (sc
.state
== SCE_C_IDENTIFIER
) {
102 if (!IsAWordChar(sc
.ch
) || (sc
.ch
== '.')) {
105 sc
.GetCurrent(s
, sizeof(s
));
107 sc
.GetCurrentLowered(s
, sizeof(s
));
109 if (keywords
.InList(s
)) {
110 lastWordWasUUID
= strcmp(s
, "uuid") == 0;
111 sc
.ChangeState(SCE_C_WORD
);
112 } else if (keywords2
.InList(s
)) {
113 sc
.ChangeState(SCE_C_WORD2
);
115 sc
.SetState(SCE_C_DEFAULT
);
117 } else if (sc
.state
== SCE_C_PREPROCESSOR
) {
118 if (stylingWithinPreprocessor
) {
119 if (IsASpace(sc
.ch
)) {
120 sc
.SetState(SCE_C_DEFAULT
);
123 if ((sc
.atLineEnd
) || (sc
.Match('/', '*')) || (sc
.Match('/', '/'))) {
124 sc
.SetState(SCE_C_DEFAULT
);
127 } else if (sc
.state
== SCE_C_COMMENT
) {
128 if (sc
.Match('*', '/')) {
130 sc
.ForwardSetState(SCE_C_DEFAULT
);
132 } else if (sc
.state
== SCE_C_COMMENTDOC
) {
133 if (sc
.Match('*', '/')) {
135 sc
.ForwardSetState(SCE_C_DEFAULT
);
136 } else if (sc
.ch
== '@' || sc
.ch
== '\\') {
137 sc
.SetState(SCE_C_COMMENTDOCKEYWORD
);
139 } else if (sc
.state
== SCE_C_COMMENTLINE
|| sc
.state
== SCE_C_COMMENTLINEDOC
) {
141 sc
.SetState(SCE_C_DEFAULT
);
144 } else if (sc
.state
== SCE_C_COMMENTDOCKEYWORD
) {
145 if (sc
.Match('*', '/')) {
146 sc
.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR
);
148 sc
.ForwardSetState(SCE_C_DEFAULT
);
149 } else if (!IsADoxygenChar(sc
.ch
)) {
152 sc
.GetCurrent(s
, sizeof(s
));
154 sc
.GetCurrentLowered(s
, sizeof(s
));
156 if (!isspace(sc
.ch
) || !keywords3
.InList(s
+ 1)) {
157 sc
.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR
);
159 sc
.SetState(SCE_C_COMMENTDOC
);
161 } else if (sc
.state
== SCE_C_STRING
) {
163 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
166 } else if (sc
.ch
== '\"') {
167 sc
.ForwardSetState(SCE_C_DEFAULT
);
168 } else if (sc
.atLineEnd
) {
169 sc
.ChangeState(SCE_C_STRINGEOL
);
170 sc
.ForwardSetState(SCE_C_DEFAULT
);
173 } else if (sc
.state
== SCE_C_CHARACTER
) {
175 sc
.ChangeState(SCE_C_STRINGEOL
);
176 sc
.ForwardSetState(SCE_C_DEFAULT
);
178 } else if (sc
.ch
== '\\') {
179 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
182 } else if (sc
.ch
== '\'') {
183 sc
.ForwardSetState(SCE_C_DEFAULT
);
185 } else if (sc
.state
== SCE_C_REGEX
) {
186 if (sc
.ch
== '\r' || sc
.ch
== '\n' || sc
.ch
== '/') {
187 sc
.ForwardSetState(SCE_C_DEFAULT
);
188 } else if (sc
.ch
== '\\') {
189 // Gobble up the quoted character
190 if (sc
.chNext
== '\\' || sc
.chNext
== '/') {
194 } else if (sc
.state
== SCE_C_VERBATIM
) {
196 if (sc
.chNext
== '\"') {
199 sc
.ForwardSetState(SCE_C_DEFAULT
);
202 } else if (sc
.state
== SCE_C_UUID
) {
203 if (sc
.ch
== '\r' || sc
.ch
== '\n' || sc
.ch
== ')') {
204 sc
.SetState(SCE_C_DEFAULT
);
208 // Determine if a new state should be entered.
209 if (sc
.state
== SCE_C_DEFAULT
) {
210 if (sc
.Match('@', '\"')) {
211 sc
.SetState(SCE_C_VERBATIM
);
213 } else if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
))) {
214 if (lastWordWasUUID
) {
215 sc
.SetState(SCE_C_UUID
);
216 lastWordWasUUID
= false;
218 sc
.SetState(SCE_C_NUMBER
);
220 } else if (IsAWordStart(sc
.ch
) || (sc
.ch
== '@')) {
221 if (lastWordWasUUID
) {
222 sc
.SetState(SCE_C_UUID
);
223 lastWordWasUUID
= false;
225 sc
.SetState(SCE_C_IDENTIFIER
);
227 } else if (sc
.Match('/', '*')) {
228 if (sc
.Match("/**") || sc
.Match("/*!")) { // Support of Qt/Doxygen doc. style
229 sc
.SetState(SCE_C_COMMENTDOC
);
231 sc
.SetState(SCE_C_COMMENT
);
233 sc
.Forward(); // Eat the * so it isn't used for the end of the comment
234 } else if (sc
.Match('/', '/')) {
235 if (sc
.Match("///") || sc
.Match("//!")) // Support of Qt/Doxygen doc. style
236 sc
.SetState(SCE_C_COMMENTLINEDOC
);
238 sc
.SetState(SCE_C_COMMENTLINE
);
239 } else if (sc
.ch
== '/' && IsOKBeforeRE(chPrevNonWhite
)) {
240 sc
.SetState(SCE_C_REGEX
);
241 } else if (sc
.ch
== '\"') {
242 sc
.SetState(SCE_C_STRING
);
243 } else if (sc
.ch
== '\'') {
244 sc
.SetState(SCE_C_CHARACTER
);
245 } else if (sc
.ch
== '#' && visibleChars
== 0) {
246 // Preprocessor commands are alone on their line
247 sc
.SetState(SCE_C_PREPROCESSOR
);
248 // Skip whitespace between # and preprocessor word
251 } while ((sc
.ch
== ' ' || sc
.ch
== '\t') && sc
.More());
253 sc
.SetState(SCE_C_DEFAULT
);
255 } else if (isoperator(static_cast<char>(sc
.ch
))) {
256 sc
.SetState(SCE_C_OPERATOR
);
261 // Reset states to begining of colourise so no surprises
262 // if different sets of lines lexed.
263 chPrevNonWhite
= ' ';
265 lastWordWasUUID
= false;
267 if (!IsASpace(sc
.ch
)) {
268 chPrevNonWhite
= sc
.ch
;
275 static bool IsStreamCommentStyle(int style
) {
276 return style
== SCE_C_COMMENT
||
277 style
== SCE_C_COMMENTDOC
||
278 style
== SCE_C_COMMENTDOCKEYWORD
||
279 style
== SCE_C_COMMENTDOCKEYWORDERROR
;
282 static bool matchKeyword(unsigned int start
, WordList
&keywords
, Accessor
&styler
, int keywordtype
) {
283 bool FoundKeyword
= false;
285 for (unsigned int i
= 0;
286 strlen(keywords
[i
]) > 0 && !FoundKeyword
;
288 if (atoi(keywords
[i
]) == keywordtype
) {
289 FoundKeyword
= styler
.Match(start
, ((char *)keywords
[i
]) + 2);
295 static bool IsCommentLine(int line
, Accessor
&styler
) {
296 unsigned int Pos
= styler
.LineStart(line
);
297 while (styler
.GetLine(Pos
) == line
) {
298 int PosStyle
= styler
.StyleAt(Pos
);
300 if ( !IsStreamCommentStyle(PosStyle
)
302 PosStyle
!= SCE_C_COMMENTLINEDOC
304 PosStyle
!= SCE_C_COMMENTLINE
306 !IsASpace(styler
.SafeGetCharAt(Pos
))
315 static void FoldBoxCppDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
318 WordList
&keywords4
= *keywordlists
[3];
320 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
321 bool foldPreprocessor
= styler
.GetPropertyInt("fold.preprocessor") != 0;
322 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
323 bool firstLine
= true;
324 unsigned int endPos
= startPos
+ length
;
325 int visibleChars
= 0;
326 int lineCurrent
= styler
.GetLine(startPos
);
327 int levelPrev
= styler
.LevelAt(lineCurrent
) & SC_FOLDLEVELNUMBERMASK
;
328 int levelCurrent
= levelPrev
;
331 int levelUnindent
= 0;
332 char chNext
= styler
[startPos
];
333 int styleNext
= styler
.StyleAt(startPos
);
334 int style
= initStyle
;
336 if (lineCurrent
== 0) {
337 levelPrevPrev
= levelPrev
;
339 levelPrevPrev
= styler
.LevelAt(lineCurrent
- 1) & SC_FOLDLEVELNUMBERMASK
;
342 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
344 chNext
= styler
.SafeGetCharAt(i
+ 1);
345 int stylePrev
= style
;
347 styleNext
= styler
.StyleAt(i
+ 1);
349 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
351 if (foldComment
&& IsStreamCommentStyle(style
)) {
352 if (!IsStreamCommentStyle(stylePrev
)) {
354 } else if (!IsStreamCommentStyle(styleNext
) && !atEOL
) {
355 // Comments don't end at end of line and the next character may be unstyled.
360 if (foldComment
&& (style
== SCE_C_COMMENTLINE
)) {
361 if ((ch
== '/') && (chNext
== '/')) {
362 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
363 if (chNext2
== '{') {
365 } else if (chNext2
== '}') {
371 if (foldPreprocessor
&& (style
== SCE_C_PREPROCESSOR
)) {
373 unsigned int j
= i
+ 1;
374 while ((j
< endPos
) && IsASpaceOrTab(styler
.SafeGetCharAt(j
))) {
378 if (styler
.Match(j
, "region") || styler
.Match(j
, "if")) {
380 } else if (styler
.Match(j
, "end")) {
386 if (style
== SCE_C_OPERATOR
388 style
== SCE_C_COMMENT
390 style
== SCE_C_COMMENTLINE
) {
394 // Special handling if line has closing brace followed by opening brace.
395 if (levelCurrent
== levelPrev
) {
401 } else if (ch
== '}') {
406 /* Check for fold header keyword at beginning of word */
407 if ((style
== SCE_C_WORD
|| style
== SCE_C_COMMENT
|| style
== SCE_C_COMMENTLINE
)
409 (style
!= stylePrev
)) {
410 if (matchKeyword(i
, keywords4
, styler
, KEYWORD_BOXHEADER
)) {
412 /* Loop backwards all empty or comment lines */
413 for (line
= lineCurrent
- 1;
416 levelCurrent
== (styler
.LevelAt(line
) & SC_FOLDLEVELNUMBERMASK
)
418 (styler
.LevelAt(line
) & SC_FOLDLEVELBOXFOOTERFLAG
) == 0
420 IsCommentLine(line
, styler
);
422 /* just loop backwards */;
426 /* Set Box header flag (if the previous line has no footer line) */
427 if ((styler
.LevelAt(line
) & SC_FOLDLEVELBOXFOOTERFLAG
) == 0) {
428 if (line
== lineCurrent
) {
429 /* in current line */
430 levelFlags
|= SC_FOLDLEVELBOXHEADERFLAG
;
432 /* at top of all preceding comment lines */
433 styler
.SetLevel(line
, styler
.LevelAt(line
)
434 | SC_FOLDLEVELBOXHEADERFLAG
);
440 if (matchKeyword(i
, keywords4
, styler
, KEYWORD_FOLDCONTRACTED
)) {
441 levelFlags
|= SC_FOLDLEVELCONTRACTED
;
446 // Compute level correction for special case: '} else {'
447 if (levelUnindent
< 0) {
448 levelPrev
+= levelUnindent
;
450 levelCurrent
+= levelUnindent
;
454 if (visibleChars
== 0 && foldCompact
)
455 lev
|= SC_FOLDLEVELWHITEFLAG
;
456 // Produce additional footer line (e.g. after closed if)
457 if (visibleChars
== 0
459 (levelPrev
< levelPrevPrev
))
460 lev
|= SC_FOLDLEVELBOXFOOTERFLAG
;
461 // Produce footer line at line before (special handling for '} else {'
462 if (levelPrev
< levelPrevPrev
) {
463 styler
.SetLevel(lineCurrent
- 1,
464 styler
.LevelAt(lineCurrent
- 1) | SC_FOLDLEVELBOXFOOTERFLAG
);
466 // Mark the fold header (the line that is always visible)
467 if ((levelCurrent
> levelPrev
) && (visibleChars
> 0))
468 lev
|= SC_FOLDLEVELHEADERFLAG
;
469 // Show a footer line at end of fold
470 if (levelCurrent
< levelPrev
)
471 lev
|= SC_FOLDLEVELBOXFOOTERFLAG
;
472 /* Show a footer line at the end of each procedure (level == SC_FOLDLEVELBASE) */
473 if ((levelPrev
== SC_FOLDLEVELBASE
)
475 (levelPrevPrev
> SC_FOLDLEVELBASE
)
477 (visibleChars
== 0)) {
478 lev
|= SC_FOLDLEVELBOXFOOTERFLAG
;
482 if (lev
!= styler
.LevelAt(lineCurrent
)) {
483 styler
.SetLevel(lineCurrent
, lev
);
487 levelPrevPrev
= levelPrev
;
488 levelPrev
= levelCurrent
;
495 if (!isspacechar(ch
))
498 // Fill in the real level of the next line, keeping the current flags as they will be filled in later
499 int flagsNext
= styler
.LevelAt(lineCurrent
) & ~SC_FOLDLEVELNUMBERMASK
;
500 styler
.SetLevel(lineCurrent
, levelPrev
| flagsNext
);
503 static void FoldNoBoxCppDoc(unsigned int startPos
, int length
, int initStyle
,
505 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
506 bool foldPreprocessor
= styler
.GetPropertyInt("fold.preprocessor") != 0;
507 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
508 unsigned int endPos
= startPos
+ length
;
509 int visibleChars
= 0;
510 int lineCurrent
= styler
.GetLine(startPos
);
511 int levelPrev
= styler
.LevelAt(lineCurrent
) & SC_FOLDLEVELNUMBERMASK
;
512 int levelCurrent
= levelPrev
;
513 char chNext
= styler
[startPos
];
514 int styleNext
= styler
.StyleAt(startPos
);
515 int style
= initStyle
;
516 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
518 chNext
= styler
.SafeGetCharAt(i
+ 1);
519 int stylePrev
= style
;
521 styleNext
= styler
.StyleAt(i
+ 1);
522 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
523 if (foldComment
&& IsStreamCommentStyle(style
)) {
524 if (!IsStreamCommentStyle(stylePrev
)) {
526 } else if (!IsStreamCommentStyle(styleNext
) && !atEOL
) {
527 // Comments don't end at end of line and the next character may be unstyled.
531 if (foldComment
&& (style
== SCE_C_COMMENTLINE
)) {
532 if ((ch
== '/') && (chNext
== '/')) {
533 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
534 if (chNext2
== '{') {
536 } else if (chNext2
== '}') {
541 if (foldPreprocessor
&& (style
== SCE_C_PREPROCESSOR
)) {
543 unsigned int j
= i
+ 1;
544 while ((j
< endPos
) && IsASpaceOrTab(styler
.SafeGetCharAt(j
))) {
547 if (styler
.Match(j
, "region") || styler
.Match(j
, "if")) {
549 } else if (styler
.Match(j
, "end")) {
554 if (style
== SCE_C_OPERATOR
) {
557 } else if (ch
== '}') {
563 if (visibleChars
== 0 && foldCompact
)
564 lev
|= SC_FOLDLEVELWHITEFLAG
;
565 if ((levelCurrent
> levelPrev
) && (visibleChars
> 0))
566 lev
|= SC_FOLDLEVELHEADERFLAG
;
567 if (lev
!= styler
.LevelAt(lineCurrent
)) {
568 styler
.SetLevel(lineCurrent
, lev
);
571 levelPrev
= levelCurrent
;
574 if (!isspacechar(ch
))
577 // Fill in the real level of the next line, keeping the current flags as they will be filled in later
578 int flagsNext
= styler
.LevelAt(lineCurrent
) & ~SC_FOLDLEVELNUMBERMASK
;
579 styler
.SetLevel(lineCurrent
, levelPrev
| flagsNext
);
582 static void FoldCppDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
585 int foldFlags
= styler
.GetPropertyInt("fold.flags") ;
586 bool foldBox
= ((foldFlags
& SC_FOLDFLAG_BOX
) == SC_FOLDFLAG_BOX
);
589 FoldBoxCppDoc(startPos
, length
, initStyle
, keywordlists
, styler
);
591 FoldNoBoxCppDoc(startPos
, length
, initStyle
, styler
);
595 static const char * const cppWordLists
[] = {
596 "Primary keywords and identifiers",
597 "Secondary keywords and identifiers",
598 "Documentation comment keywords",
599 "Fold header keywords",
603 static void ColouriseCppDocSensitive(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
605 ColouriseCppDoc(startPos
, length
, initStyle
, keywordlists
, styler
, true);
608 static void ColouriseCppDocInsensitive(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
610 ColouriseCppDoc(startPos
, length
, initStyle
, keywordlists
, styler
, false);
613 LexerModule
lmCPP(SCLEX_CPP
, ColouriseCppDocSensitive
, "cpp", FoldCppDoc
, cppWordLists
);
614 LexerModule
lmCPPNoCase(SCLEX_CPPNOCASE
, ColouriseCppDocInsensitive
, "cppnocase", FoldCppDoc
, cppWordLists
);
615 LexerModule
lmTCL(SCLEX_TCL
, ColouriseCppDocSensitive
, "tcl", FoldCppDoc
, cppWordLists
);