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"
22 #include "CharacterSet.h"
25 using namespace Scintilla
;
28 static bool IsSpaceEquiv(int state
) {
29 return (state
<= SCE_C_COMMENTDOC
) ||
30 // including SCE_C_DEFAULT, SCE_C_COMMENT, SCE_C_COMMENTLINE
31 (state
== SCE_C_COMMENTLINEDOC
) || (state
== SCE_C_COMMENTDOCKEYWORD
) ||
32 (state
== SCE_C_COMMENTDOCKEYWORDERROR
);
35 // Preconditions: sc.currentPos points to a character after '+' or '-'.
36 // The test for pos reaching 0 should be redundant,
37 // and is in only for safety measures.
38 // Limitation: this code will give the incorrect answer for code like
40 // Putting a space between the '++' post-inc operator and the '+' binary op
41 // fixes this, and is highly recommended for readability anyway.
42 static bool FollowsPostfixOperator(StyleContext
&sc
, Accessor
&styler
) {
43 int pos
= (int) sc
.currentPos
;
45 char ch
= styler
[pos
];
46 if (ch
== '+' || ch
== '-') {
47 return styler
[pos
- 1] == ch
;
53 static void ColouriseCppDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
54 Accessor
&styler
, bool caseSensitive
) {
56 WordList
&keywords
= *keywordlists
[0];
57 WordList
&keywords2
= *keywordlists
[1];
58 WordList
&keywords3
= *keywordlists
[2];
59 WordList
&keywords4
= *keywordlists
[3];
61 // property styling.within.preprocessor
62 // For C++ code, determines whether all preprocessor code is styled in the preprocessor style (0, the default)
63 // or only from the initial # to the end of the command word(1).
64 bool stylingWithinPreprocessor
= styler
.GetPropertyInt("styling.within.preprocessor") != 0;
66 CharacterSet
setOKBeforeRE(CharacterSet::setNone
, "([{=,:;!%^&*|?~+-");
67 CharacterSet
setCouldBePostOp(CharacterSet::setNone
, "+-");
69 CharacterSet
setDoxygen(CharacterSet::setAlpha
, "$@\\&<>#{}[]");
71 CharacterSet
setWordStart(CharacterSet::setAlpha
, "_", 0x80, true);
72 CharacterSet
setWord(CharacterSet::setAlphaNum
, "._", 0x80, true);
74 // property lexer.cpp.allow.dollars
75 // Set to 0 to disallow the '$' character in identifiers with the cpp lexer.
76 if (styler
.GetPropertyInt("lexer.cpp.allow.dollars", 1) != 0) {
77 setWordStart
.Add('$');
81 int chPrevNonWhite
= ' ';
83 bool lastWordWasUUID
= false;
84 int styleBeforeDCKeyword
= SCE_C_DEFAULT
;
85 bool continuationLine
= false;
86 bool isIncludePreprocessor
= false;
88 if (initStyle
== SCE_C_PREPROCESSOR
) {
89 // Set continuationLine if last character of previous line is '\'
90 int lineCurrent
= styler
.GetLine(startPos
);
91 if (lineCurrent
> 0) {
92 int chBack
= styler
.SafeGetCharAt(startPos
-1, 0);
93 int chBack2
= styler
.SafeGetCharAt(startPos
-2, 0);
94 int lineEndChar
= '!';
95 if (chBack2
== '\r' && chBack
== '\n') {
96 lineEndChar
= styler
.SafeGetCharAt(startPos
-3, 0);
97 } else if (chBack
== '\n' || chBack
== '\r') {
98 lineEndChar
= chBack2
;
100 continuationLine
= lineEndChar
== '\\';
104 // look back to set chPrevNonWhite properly for better regex colouring
107 while (--back
&& IsSpaceEquiv(styler
.StyleAt(back
)))
109 if (styler
.StyleAt(back
) == SCE_C_OPERATOR
) {
110 chPrevNonWhite
= styler
.SafeGetCharAt(back
);
114 StyleContext
sc(startPos
, length
, initStyle
, styler
);
116 for (; sc
.More(); sc
.Forward()) {
118 if (sc
.atLineStart
) {
119 if (sc
.state
== SCE_C_STRING
) {
120 // Prevent SCE_C_STRINGEOL from leaking back to previous line which
121 // ends with a line continuation by locking in the state upto this position.
122 sc
.SetState(SCE_C_STRING
);
124 // Reset states to begining of colourise so no surprises
125 // if different sets of lines lexed.
127 lastWordWasUUID
= false;
128 isIncludePreprocessor
= false;
131 // Handle line continuation generically.
133 if (sc
.chNext
== '\n' || sc
.chNext
== '\r') {
135 if (sc
.ch
== '\r' && sc
.chNext
== '\n') {
138 continuationLine
= true;
143 // Determine if the current state should terminate.
146 sc
.SetState(SCE_C_DEFAULT
);
149 // We accept almost anything because of hex. and number suffixes
150 if (!setWord
.Contains(sc
.ch
)) {
151 sc
.SetState(SCE_C_DEFAULT
);
154 case SCE_C_IDENTIFIER
:
155 if (!setWord
.Contains(sc
.ch
) || (sc
.ch
== '.')) {
158 sc
.GetCurrent(s
, sizeof(s
));
160 sc
.GetCurrentLowered(s
, sizeof(s
));
162 if (keywords
.InList(s
)) {
163 lastWordWasUUID
= strcmp(s
, "uuid") == 0;
164 sc
.ChangeState(SCE_C_WORD
);
165 } else if (keywords2
.InList(s
)) {
166 sc
.ChangeState(SCE_C_WORD2
);
167 } else if (keywords4
.InList(s
)) {
168 sc
.ChangeState(SCE_C_GLOBALCLASS
);
170 sc
.SetState(SCE_C_DEFAULT
);
173 case SCE_C_PREPROCESSOR
:
174 if (sc
.atLineStart
&& !continuationLine
) {
175 sc
.SetState(SCE_C_DEFAULT
);
176 } else if (stylingWithinPreprocessor
) {
177 if (IsASpace(sc
.ch
)) {
178 sc
.SetState(SCE_C_DEFAULT
);
181 if (sc
.Match('/', '*') || sc
.Match('/', '/')) {
182 sc
.SetState(SCE_C_DEFAULT
);
187 if (sc
.Match('*', '/')) {
189 sc
.ForwardSetState(SCE_C_DEFAULT
);
192 case SCE_C_COMMENTDOC
:
193 if (sc
.Match('*', '/')) {
195 sc
.ForwardSetState(SCE_C_DEFAULT
);
196 } else if (sc
.ch
== '@' || sc
.ch
== '\\') { // JavaDoc and Doxygen support
197 // Verify that we have the conditions to mark a comment-doc-keyword
198 if ((IsASpace(sc
.chPrev
) || sc
.chPrev
== '*') && (!IsASpace(sc
.chNext
))) {
199 styleBeforeDCKeyword
= SCE_C_COMMENTDOC
;
200 sc
.SetState(SCE_C_COMMENTDOCKEYWORD
);
204 case SCE_C_COMMENTLINE
:
205 if (sc
.atLineStart
) {
206 sc
.SetState(SCE_C_DEFAULT
);
209 case SCE_C_COMMENTLINEDOC
:
210 if (sc
.atLineStart
) {
211 sc
.SetState(SCE_C_DEFAULT
);
212 } else if (sc
.ch
== '@' || sc
.ch
== '\\') { // JavaDoc and Doxygen support
213 // Verify that we have the conditions to mark a comment-doc-keyword
214 if ((IsASpace(sc
.chPrev
) || sc
.chPrev
== '/' || sc
.chPrev
== '!') && (!IsASpace(sc
.chNext
))) {
215 styleBeforeDCKeyword
= SCE_C_COMMENTLINEDOC
;
216 sc
.SetState(SCE_C_COMMENTDOCKEYWORD
);
220 case SCE_C_COMMENTDOCKEYWORD
:
221 if ((styleBeforeDCKeyword
== SCE_C_COMMENTDOC
) && sc
.Match('*', '/')) {
222 sc
.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR
);
224 sc
.ForwardSetState(SCE_C_DEFAULT
);
225 } else if (!setDoxygen
.Contains(sc
.ch
)) {
228 sc
.GetCurrent(s
, sizeof(s
));
230 sc
.GetCurrentLowered(s
, sizeof(s
));
232 if (!IsASpace(sc
.ch
) || !keywords3
.InList(s
+ 1)) {
233 sc
.ChangeState(SCE_C_COMMENTDOCKEYWORDERROR
);
235 sc
.SetState(styleBeforeDCKeyword
);
240 sc
.ChangeState(SCE_C_STRINGEOL
);
241 } else if (isIncludePreprocessor
) {
243 sc
.ForwardSetState(SCE_C_DEFAULT
);
244 isIncludePreprocessor
= false;
246 } else if (sc
.ch
== '\\') {
247 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
250 } else if (sc
.ch
== '\"') {
251 sc
.ForwardSetState(SCE_C_DEFAULT
);
254 case SCE_C_CHARACTER
:
256 sc
.ChangeState(SCE_C_STRINGEOL
);
257 } else if (sc
.ch
== '\\') {
258 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
261 } else if (sc
.ch
== '\'') {
262 sc
.ForwardSetState(SCE_C_DEFAULT
);
266 if (sc
.atLineStart
) {
267 sc
.SetState(SCE_C_DEFAULT
);
268 } else if (sc
.ch
== '/') {
270 while ((sc
.ch
< 0x80) && islower(sc
.ch
))
271 sc
.Forward(); // gobble regex flags
272 sc
.SetState(SCE_C_DEFAULT
);
273 } else if (sc
.ch
== '\\') {
274 // Gobble up the quoted character
275 if (sc
.chNext
== '\\' || sc
.chNext
== '/') {
280 case SCE_C_STRINGEOL
:
281 if (sc
.atLineStart
) {
282 sc
.SetState(SCE_C_DEFAULT
);
287 if (sc
.chNext
== '\"') {
290 sc
.ForwardSetState(SCE_C_DEFAULT
);
295 if (sc
.ch
== '\r' || sc
.ch
== '\n' || sc
.ch
== ')') {
296 sc
.SetState(SCE_C_DEFAULT
);
300 // Determine if a new state should be entered.
301 if (sc
.state
== SCE_C_DEFAULT
) {
302 if (sc
.Match('@', '\"')) {
303 sc
.SetState(SCE_C_VERBATIM
);
305 } else if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
))) {
306 if (lastWordWasUUID
) {
307 sc
.SetState(SCE_C_UUID
);
308 lastWordWasUUID
= false;
310 sc
.SetState(SCE_C_NUMBER
);
312 } else if (setWordStart
.Contains(sc
.ch
) || (sc
.ch
== '@')) {
313 if (lastWordWasUUID
) {
314 sc
.SetState(SCE_C_UUID
);
315 lastWordWasUUID
= false;
317 sc
.SetState(SCE_C_IDENTIFIER
);
319 } else if (sc
.Match('/', '*')) {
320 if (sc
.Match("/**") || sc
.Match("/*!")) { // Support of Qt/Doxygen doc. style
321 sc
.SetState(SCE_C_COMMENTDOC
);
323 sc
.SetState(SCE_C_COMMENT
);
325 sc
.Forward(); // Eat the * so it isn't used for the end of the comment
326 } else if (sc
.Match('/', '/')) {
327 if ((sc
.Match("///") && !sc
.Match("////")) || sc
.Match("//!"))
328 // Support of Qt/Doxygen doc. style
329 sc
.SetState(SCE_C_COMMENTLINEDOC
);
331 sc
.SetState(SCE_C_COMMENTLINE
);
332 } else if (sc
.ch
== '/' && setOKBeforeRE
.Contains(chPrevNonWhite
) &&
333 (!setCouldBePostOp
.Contains(chPrevNonWhite
) || !FollowsPostfixOperator(sc
, styler
))) {
334 sc
.SetState(SCE_C_REGEX
); // JavaScript's RegEx
335 } else if (sc
.ch
== '\"') {
336 sc
.SetState(SCE_C_STRING
);
337 isIncludePreprocessor
= false; // ensure that '>' won't end the string
338 } else if (isIncludePreprocessor
&& sc
.ch
== '<') {
339 sc
.SetState(SCE_C_STRING
);
340 } else if (sc
.ch
== '\'') {
341 sc
.SetState(SCE_C_CHARACTER
);
342 } else if (sc
.ch
== '#' && visibleChars
== 0) {
343 // Preprocessor commands are alone on their line
344 sc
.SetState(SCE_C_PREPROCESSOR
);
345 // Skip whitespace between # and preprocessor word
348 } while ((sc
.ch
== ' ' || sc
.ch
== '\t') && sc
.More());
350 sc
.SetState(SCE_C_DEFAULT
);
351 } else if (sc
.Match("include")) {
352 isIncludePreprocessor
= true;
354 } else if (isoperator(static_cast<char>(sc
.ch
))) {
355 sc
.SetState(SCE_C_OPERATOR
);
359 if (!IsASpace(sc
.ch
) && !IsSpaceEquiv(sc
.state
)) {
360 chPrevNonWhite
= sc
.ch
;
363 continuationLine
= false;
368 static bool IsStreamCommentStyle(int style
) {
369 return style
== SCE_C_COMMENT
||
370 style
== SCE_C_COMMENTDOC
||
371 style
== SCE_C_COMMENTDOCKEYWORD
||
372 style
== SCE_C_COMMENTDOCKEYWORDERROR
;
375 // Store both the current line's fold level and the next lines in the
376 // level store to make it easy to pick up with each increment
377 // and to make it possible to fiddle the current level for "} else {".
378 static void FoldCppDoc(unsigned int startPos
, int length
, int initStyle
,
379 WordList
*[], Accessor
&styler
) {
381 // property fold.comment
382 // This option enables folding multi-line comments when using the C++ lexer.
383 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
385 // property fold.cpp.comment.explicit
386 // Set this property to 0 to disable folding explicit fold points when fold.comment=1.
387 // Explicit fold points allows adding extra folding by placing a //{ comment at the start and a //}
388 // at the end of a section that should fold.
389 bool foldCommentExplicit
= foldComment
&&
390 styler
.GetPropertyInt("fold.cpp.comment.explicit", 1) != 0;
392 // property fold.preprocessor
393 // This option enables folding preprocessor directives when using the C++ lexer.
394 // Includes C#'s explicit #region and #endregion folding directives.
395 bool foldPreprocessor
= styler
.GetPropertyInt("fold.preprocessor") != 0;
397 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
399 // property fold.at.else
400 // This option enables C++ folding on a "} else {" line of an if statement.
401 bool foldAtElse
= styler
.GetPropertyInt("fold.at.else", 0) != 0;
403 unsigned int endPos
= startPos
+ length
;
404 int visibleChars
= 0;
405 int lineCurrent
= styler
.GetLine(startPos
);
406 int levelCurrent
= SC_FOLDLEVELBASE
;
408 levelCurrent
= styler
.LevelAt(lineCurrent
-1) >> 16;
409 int levelMinCurrent
= levelCurrent
;
410 int levelNext
= levelCurrent
;
411 char chNext
= styler
[startPos
];
412 int styleNext
= styler
.StyleAt(startPos
);
413 int style
= initStyle
;
414 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
416 chNext
= styler
.SafeGetCharAt(i
+ 1);
417 int stylePrev
= style
;
419 styleNext
= styler
.StyleAt(i
+ 1);
420 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
421 if (foldComment
&& IsStreamCommentStyle(style
)) {
422 if (!IsStreamCommentStyle(stylePrev
) && (stylePrev
!= SCE_C_COMMENTLINEDOC
)) {
424 } else if (!IsStreamCommentStyle(styleNext
) && (styleNext
!= SCE_C_COMMENTLINEDOC
) && !atEOL
) {
425 // Comments don't end at end of line and the next character may be unstyled.
429 if (foldCommentExplicit
&& (style
== SCE_C_COMMENTLINE
)) {
430 if ((ch
== '/') && (chNext
== '/')) {
431 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
432 if (chNext2
== '{') {
434 } else if (chNext2
== '}') {
439 if (foldPreprocessor
&& (style
== SCE_C_PREPROCESSOR
)) {
441 unsigned int j
= i
+ 1;
442 while ((j
< endPos
) && IsASpaceOrTab(styler
.SafeGetCharAt(j
))) {
445 if (styler
.Match(j
, "region") || styler
.Match(j
, "if")) {
447 } else if (styler
.Match(j
, "end")) {
452 if (style
== SCE_C_OPERATOR
) {
454 // Measure the minimum before a '{' to allow
455 // folding on "} else {"
456 if (levelMinCurrent
> levelNext
) {
457 levelMinCurrent
= levelNext
;
460 } else if (ch
== '}') {
466 if (atEOL
|| (i
== endPos
-1)) {
467 int levelUse
= levelCurrent
;
469 levelUse
= levelMinCurrent
;
471 int lev
= levelUse
| levelNext
<< 16;
472 if (visibleChars
== 0 && foldCompact
)
473 lev
|= SC_FOLDLEVELWHITEFLAG
;
474 if (levelUse
< levelNext
)
475 lev
|= SC_FOLDLEVELHEADERFLAG
;
476 if (lev
!= styler
.LevelAt(lineCurrent
)) {
477 styler
.SetLevel(lineCurrent
, lev
);
480 levelCurrent
= levelNext
;
481 levelMinCurrent
= levelCurrent
;
482 if (atEOL
&& (i
== static_cast<unsigned int>(styler
.Length()-1))) {
483 // There is an empty line at end of file so give it same level and empty
484 styler
.SetLevel(lineCurrent
, (levelCurrent
| levelCurrent
<< 16) | SC_FOLDLEVELWHITEFLAG
);
491 static const char *const cppWordLists
[] = {
492 "Primary keywords and identifiers",
493 "Secondary keywords and identifiers",
494 "Documentation comment keywords",
496 "Global classes and typedefs",
500 static void ColouriseCppDocSensitive(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
502 ColouriseCppDoc(startPos
, length
, initStyle
, keywordlists
, styler
, true);
505 static void ColouriseCppDocInsensitive(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
507 ColouriseCppDoc(startPos
, length
, initStyle
, keywordlists
, styler
, false);
510 LexerModule
lmCPP(SCLEX_CPP
, ColouriseCppDocSensitive
, "cpp", FoldCppDoc
, cppWordLists
);
511 LexerModule
lmCPPNoCase(SCLEX_CPPNOCASE
, ColouriseCppDocInsensitive
, "cppnocase", FoldCppDoc
, cppWordLists
);