Support more folding icon styles: arrows, +/- and no lines
[geany-mirror.git] / scintilla / LexD.cxx
blob4c4bcb343e81bc5a824f3013d411fc6d510abf4b
1 /** @file LexD.cxx
2 ** Lexer for D.
3 **
4 ** Copyright (c) 2006 by Waldemar Augustyn <waldemar@wdmsys.com>
5 **/
6 // Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org>
7 // The License.txt file describes the conditions under which this software may be distributed.
9 #include <stdlib.h>
10 #include <string.h>
11 #include <ctype.h>
12 #include <stdio.h>
13 #include <stdarg.h>
15 #include "Platform.h"
17 #include "PropSet.h"
18 #include "Accessor.h"
19 #include "StyleContext.h"
20 #include "KeyWords.h"
21 #include "Scintilla.h"
22 #include "SciLexer.h"
24 #ifdef SCI_NAMESPACE
25 using namespace Scintilla;
26 #endif
28 /* Nested comments require keeping the value of the nesting level for every
29 position in the document. But since scintilla always styles line by line,
30 we only need to store one value per line. The non-negative number indicates
31 nesting level at the end of the line.
34 // Underscore, letter, digit and universal alphas from C99 Appendix D.
36 static bool IsWordStart(int ch) {
37 return (isascii(ch) && (isalpha(ch) || ch == '_')) || !isascii(ch);
40 static bool IsWord(int ch) {
41 return (isascii(ch) && (isalnum(ch) || ch == '_')) || !isascii(ch);
44 static bool IsDoxygen(int ch) {
45 if (isascii(ch) && islower(ch))
46 return true;
47 if (ch == '$' || ch == '@' || ch == '\\' ||
48 ch == '&' || ch == '#' || ch == '<' || ch == '>' ||
49 ch == '{' || ch == '}' || ch == '[' || ch == ']')
50 return true;
51 return false;
54 static bool IsStringSuffix(int ch) {
55 return ch == 'c' || ch == 'w' || ch == 'd';
59 static void ColouriseDoc(unsigned int startPos, int length, int initStyle,
60 WordList *keywordlists[], Accessor &styler, bool caseSensitive) {
62 WordList &keywords = *keywordlists[0];
63 WordList &keywords2 = *keywordlists[1];
64 WordList &keywords3 = *keywordlists[2]; //doxygen
65 WordList &keywords4 = *keywordlists[3];
66 WordList &keywords5 = *keywordlists[4];
67 WordList &keywords6 = *keywordlists[5];
68 WordList &keywords7 = *keywordlists[6];
70 int styleBeforeDCKeyword = SCE_D_DEFAULT;
72 StyleContext sc(startPos, length, initStyle, styler);
74 int curLine = styler.GetLine(startPos);
75 int curNcLevel = curLine > 0? styler.GetLineState(curLine-1): 0;
76 bool numFloat = false; // Float literals have '+' and '-' signs
77 bool numHex = false;
79 for (; sc.More(); sc.Forward()) {
81 if (sc.atLineStart) {
82 curLine = styler.GetLine(sc.currentPos);
83 styler.SetLineState(curLine, curNcLevel);
86 // Determine if the current state should terminate.
87 switch (sc.state) {
88 case SCE_D_OPERATOR:
89 sc.SetState(SCE_D_DEFAULT);
90 break;
91 case SCE_D_NUMBER:
92 // We accept almost anything because of hex. and number suffixes
93 if (isascii(sc.ch) && (isalnum(sc.ch) || sc.ch == '_')) {
94 continue;
95 } else if (sc.ch == '.' && sc.chNext != '.' && !numFloat) {
96 // Don't parse 0..2 as number.
97 numFloat=true;
98 continue;
99 } else if ( ( sc.ch == '-' || sc.ch == '+' ) && ( /*sign and*/
100 ( !numHex && ( sc.chPrev == 'e' || sc.chPrev == 'E' ) ) || /*decimal or*/
101 ( sc.chPrev == 'p' || sc.chPrev == 'P' ) ) ) { /*hex*/
102 // Parse exponent sign in float literals: 2e+10 0x2e+10
103 continue;
104 } else {
105 sc.SetState(SCE_D_DEFAULT);
107 break;
108 case SCE_D_IDENTIFIER:
109 if (!IsWord(sc.ch)) {
110 char s[1000];
111 if (caseSensitive) {
112 sc.GetCurrent(s, sizeof(s));
113 } else {
114 sc.GetCurrentLowered(s, sizeof(s));
116 if (keywords.InList(s)) {
117 sc.ChangeState(SCE_D_WORD);
118 } else if (keywords2.InList(s)) {
119 sc.ChangeState(SCE_D_WORD2);
120 } else if (keywords4.InList(s)) {
121 sc.ChangeState(SCE_D_TYPEDEF);
122 } else if (keywords5.InList(s)) {
123 sc.ChangeState(SCE_D_WORD5);
124 } else if (keywords6.InList(s)) {
125 sc.ChangeState(SCE_D_WORD6);
126 } else if (keywords7.InList(s)) {
127 sc.ChangeState(SCE_D_WORD7);
129 sc.SetState(SCE_D_DEFAULT);
131 break;
132 case SCE_D_COMMENT:
133 if (sc.Match('*', '/')) {
134 sc.Forward();
135 sc.ForwardSetState(SCE_D_DEFAULT);
137 break;
138 case SCE_D_COMMENTDOC:
139 if (sc.Match('*', '/')) {
140 sc.Forward();
141 sc.ForwardSetState(SCE_D_DEFAULT);
142 } else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
143 // Verify that we have the conditions to mark a comment-doc-keyword
144 if ((IsASpace(sc.chPrev) || sc.chPrev == '*') && (!IsASpace(sc.chNext))) {
145 styleBeforeDCKeyword = SCE_D_COMMENTDOC;
146 sc.SetState(SCE_D_COMMENTDOCKEYWORD);
149 break;
150 case SCE_D_COMMENTLINE:
151 if (sc.atLineStart) {
152 sc.SetState(SCE_D_DEFAULT);
154 break;
155 case SCE_D_COMMENTLINEDOC:
156 if (sc.atLineStart) {
157 sc.SetState(SCE_D_DEFAULT);
158 } else if (sc.ch == '@' || sc.ch == '\\') { // JavaDoc and Doxygen support
159 // Verify that we have the conditions to mark a comment-doc-keyword
160 if ((IsASpace(sc.chPrev) || sc.chPrev == '/' || sc.chPrev == '!') && (!IsASpace(sc.chNext))) {
161 styleBeforeDCKeyword = SCE_D_COMMENTLINEDOC;
162 sc.SetState(SCE_D_COMMENTDOCKEYWORD);
165 break;
166 case SCE_D_COMMENTDOCKEYWORD:
167 if ((styleBeforeDCKeyword == SCE_D_COMMENTDOC) && sc.Match('*', '/')) {
168 sc.ChangeState(SCE_D_COMMENTDOCKEYWORDERROR);
169 sc.Forward();
170 sc.ForwardSetState(SCE_D_DEFAULT);
171 } else if (!IsDoxygen(sc.ch)) {
172 char s[100];
173 if (caseSensitive) {
174 sc.GetCurrent(s, sizeof(s));
175 } else {
176 sc.GetCurrentLowered(s, sizeof(s));
178 if (!IsASpace(sc.ch) || !keywords3.InList(s + 1)) {
179 sc.ChangeState(SCE_D_COMMENTDOCKEYWORDERROR);
181 sc.SetState(styleBeforeDCKeyword);
183 break;
184 case SCE_D_COMMENTNESTED:
185 if (sc.Match('+', '/')) {
186 if (curNcLevel > 0)
187 curNcLevel -= 1;
188 curLine = styler.GetLine(sc.currentPos);
189 styler.SetLineState(curLine, curNcLevel);
190 sc.Forward();
191 if (curNcLevel == 0) {
192 sc.ForwardSetState(SCE_D_DEFAULT);
194 } else if (sc.Match('/','+')) {
195 curNcLevel += 1;
196 curLine = styler.GetLine(sc.currentPos);
197 styler.SetLineState(curLine, curNcLevel);
198 sc.Forward();
200 break;
201 case SCE_D_STRING:
202 if (sc.ch == '\\') {
203 if (sc.chNext == '"' || sc.chNext == '\\') {
204 sc.Forward();
206 } else if (sc.ch == '"') {
207 if(IsStringSuffix(sc.chNext))
208 sc.Forward();
209 sc.ForwardSetState(SCE_D_DEFAULT);
211 break;
212 case SCE_D_CHARACTER:
213 if (sc.atLineEnd) {
214 sc.ChangeState(SCE_D_STRINGEOL);
215 } else if (sc.ch == '\\') {
216 if (sc.chNext == '\'' || sc.chNext == '\\') {
217 sc.Forward();
219 } else if (sc.ch == '\'') {
220 // Char has no suffixes
221 sc.ForwardSetState(SCE_D_DEFAULT);
223 break;
224 case SCE_D_STRINGEOL:
225 if (sc.atLineStart) {
226 sc.SetState(SCE_D_DEFAULT);
228 break;
229 case SCE_D_STRINGB:
230 if (sc.ch == '`') {
231 if(IsStringSuffix(sc.chNext))
232 sc.Forward();
233 sc.ForwardSetState(SCE_D_DEFAULT);
235 break;
236 case SCE_D_STRINGR:
237 if (sc.ch == '"') {
238 if(IsStringSuffix(sc.chNext))
239 sc.Forward();
240 sc.ForwardSetState(SCE_D_DEFAULT);
242 break;
245 // Determine if a new state should be entered.
246 if (sc.state == SCE_D_DEFAULT) {
247 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
248 sc.SetState(SCE_D_NUMBER);
249 numFloat = sc.ch == '.';
250 // Remember hex literal
251 numHex = sc.ch == '0' && ( sc.chNext == 'x' || sc.chNext == 'X' );
252 } else if ( (sc.ch == 'r' || sc.ch == 'x' || sc.ch == 'q')
253 && sc.chNext == '"' ) {
254 // Limited support for hex and delimited strings: parse as r""
255 sc.SetState(SCE_D_STRINGR);
256 sc.Forward();
257 } else if (IsWordStart(sc.ch) || sc.ch == '$') {
258 sc.SetState(SCE_D_IDENTIFIER);
259 } else if (sc.Match('/','+')) {
260 curNcLevel += 1;
261 curLine = styler.GetLine(sc.currentPos);
262 styler.SetLineState(curLine, curNcLevel);
263 sc.SetState(SCE_D_COMMENTNESTED);
264 sc.Forward();
265 } else if (sc.Match('/', '*')) {
266 if (sc.Match("/**") || sc.Match("/*!")) { // Support of Qt/Doxygen doc. style
267 sc.SetState(SCE_D_COMMENTDOC);
268 } else {
269 sc.SetState(SCE_D_COMMENT);
271 sc.Forward(); // Eat the * so it isn't used for the end of the comment
272 } else if (sc.Match('/', '/')) {
273 if ((sc.Match("///") && !sc.Match("////")) || sc.Match("//!"))
274 // Support of Qt/Doxygen doc. style
275 sc.SetState(SCE_D_COMMENTLINEDOC);
276 else
277 sc.SetState(SCE_D_COMMENTLINE);
278 } else if (sc.ch == '"') {
279 sc.SetState(SCE_D_STRING);
280 } else if (sc.ch == '\'') {
281 sc.SetState(SCE_D_CHARACTER);
282 } else if (sc.ch == '`') {
283 sc.SetState(SCE_D_STRINGB);
284 } else if (isoperator(static_cast<char>(sc.ch))) {
285 sc.SetState(SCE_D_OPERATOR);
286 if (sc.ch == '.' && sc.chNext == '.') sc.Forward(); // Range operator
290 sc.Complete();
293 static bool IsStreamCommentStyle(int style) {
294 return style == SCE_D_COMMENT ||
295 style == SCE_D_COMMENTDOC ||
296 style == SCE_D_COMMENTDOCKEYWORD ||
297 style == SCE_D_COMMENTDOCKEYWORDERROR;
300 // Store both the current line's fold level and the next lines in the
301 // level store to make it easy to pick up with each increment
302 // and to make it possible to fiddle the current level for "} else {".
303 static void FoldDoc(unsigned int startPos, int length, int initStyle, Accessor &styler) {
304 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
305 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
307 // property lexer.d.fold.at.else
308 // This option enables D folding on a "} else {" line of an if statement.
309 bool foldAtElse = styler.GetPropertyInt("lexer.d.fold.at.else",
310 styler.GetPropertyInt("fold.at.else", 0)) != 0;
311 unsigned int endPos = startPos + length;
312 int visibleChars = 0;
313 int lineCurrent = styler.GetLine(startPos);
314 int levelCurrent = SC_FOLDLEVELBASE;
315 if (lineCurrent > 0)
316 levelCurrent = styler.LevelAt(lineCurrent-1) >> 16;
317 int levelMinCurrent = levelCurrent;
318 int levelNext = levelCurrent;
319 char chNext = styler[startPos];
320 int styleNext = styler.StyleAt(startPos);
321 int style = initStyle;
322 for (unsigned int i = startPos; i < endPos; i++) {
323 char ch = chNext;
324 chNext = styler.SafeGetCharAt(i + 1);
325 int stylePrev = style;
326 style = styleNext;
327 styleNext = styler.StyleAt(i + 1);
328 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
329 if (foldComment && IsStreamCommentStyle(style)) {
330 if (!IsStreamCommentStyle(stylePrev)) {
331 levelNext++;
332 } else if (!IsStreamCommentStyle(styleNext) && !atEOL) {
333 // Comments don't end at end of line and the next character may be unstyled.
334 levelNext--;
337 if (style == SCE_D_OPERATOR) {
338 if (ch == '{') {
339 // Measure the minimum before a '{' to allow
340 // folding on "} else {"
341 if (levelMinCurrent > levelNext) {
342 levelMinCurrent = levelNext;
344 levelNext++;
345 } else if (ch == '}') {
346 levelNext--;
349 if (atEOL) {
350 if (foldComment) { // Handle nested comments
351 int nc;
352 nc = styler.GetLineState(lineCurrent);
353 nc -= lineCurrent>0? styler.GetLineState(lineCurrent-1): 0;
354 levelNext += nc;
356 int levelUse = levelCurrent;
357 if (foldAtElse) {
358 levelUse = levelMinCurrent;
360 int lev = levelUse | levelNext << 16;
361 if (visibleChars == 0 && foldCompact)
362 lev |= SC_FOLDLEVELWHITEFLAG;
363 if (levelUse < levelNext)
364 lev |= SC_FOLDLEVELHEADERFLAG;
365 if (lev != styler.LevelAt(lineCurrent)) {
366 styler.SetLevel(lineCurrent, lev);
368 lineCurrent++;
369 levelCurrent = levelNext;
370 levelMinCurrent = levelCurrent;
371 visibleChars = 0;
373 if (!IsASpace(ch))
374 visibleChars++;
378 static void FoldDDoc(unsigned int startPos, int length, int initStyle,
379 WordList *[], Accessor &styler) {
380 FoldDoc(startPos, length, initStyle, styler);
383 static const char * const dWordLists[] = {
384 "Primary keywords and identifiers",
385 "Secondary keywords and identifiers",
386 "Documentation comment keywords",
387 "Type definitions and aliases",
388 "Keywords 5",
389 "Keywords 6",
390 "Keywords 7",
394 static void ColouriseDDoc(unsigned int startPos, int length,
395 int initStyle, WordList *keywordlists[], Accessor &styler) {
396 ColouriseDoc(startPos, length, initStyle, keywordlists, styler, true);
399 LexerModule lmD(SCLEX_D, ColouriseDDoc, "d", FoldDDoc, dWordLists);