1 | // Scintilla source code edit control |
2 | /** @file LexGDScript.cxx |
3 | ** Lexer for GDScript. |
4 | **/ |
5 | // Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org> |
6 | // Heavily modified later for GDScript |
7 | // The License.txt file describes the conditions under which this software may be distributed. |
8 | |
9 | #include <cstdlib> |
10 | #include <cassert> |
11 | #include <cstring> |
12 | |
13 | #include <string> |
14 | #include <string_view> |
15 | #include <vector> |
16 | #include <map> |
17 | #include <algorithm> |
18 | #include <functional> |
19 | |
20 | #include "ILexer.h" |
21 | #include "Scintilla.h" |
22 | #include "SciLexer.h" |
23 | |
24 | #include "StringCopy.h" |
25 | #include "WordList.h" |
26 | #include "LexAccessor.h" |
27 | #include "Accessor.h" |
28 | #include "StyleContext.h" |
29 | #include "CharacterSet.h" |
30 | #include "CharacterCategory.h" |
31 | #include "LexerModule.h" |
32 | #include "OptionSet.h" |
33 | #include "SubStyles.h" |
34 | #include "DefaultLexer.h" |
35 | |
36 | using namespace Scintilla; |
37 | using namespace Lexilla; |
38 | |
39 | namespace { |
40 | |
41 | enum kwType { kwOther, kwClass, kwDef, kwExtends}; |
42 | |
43 | constexpr int indicatorWhitespace = 1; |
44 | |
45 | bool IsGDStringStart(int ch) { |
46 | return (ch == '\'' || ch == '"'); |
47 | } |
48 | |
49 | bool (Accessor &styler, Sci_Position pos, Sci_Position len) { |
50 | return len > 0 && styler[pos] == '#'; |
51 | } |
52 | |
53 | constexpr bool IsGDSingleQuoteStringState(int st) noexcept { |
54 | return ((st == SCE_GD_CHARACTER) || (st == SCE_GD_STRING)); |
55 | } |
56 | |
57 | constexpr bool IsGDTripleQuoteStringState(int st) noexcept { |
58 | return ((st == SCE_GD_TRIPLE) || (st == SCE_GD_TRIPLEDOUBLE)); |
59 | } |
60 | |
61 | char GetGDStringQuoteChar(int st) noexcept { |
62 | if ((st == SCE_GD_CHARACTER) || (st == SCE_GD_TRIPLE)) |
63 | return '\''; |
64 | if ((st == SCE_GD_STRING) || (st == SCE_GD_TRIPLEDOUBLE)) |
65 | return '"'; |
66 | |
67 | return '\0'; |
68 | } |
69 | |
70 | /* Return the state to use for the string starting at i; *nextIndex will be set to the first index following the quote(s) */ |
71 | int GetGDStringState(Accessor &styler, Sci_Position i, Sci_PositionU *nextIndex) { |
72 | char ch = styler.SafeGetCharAt(i); |
73 | char chNext = styler.SafeGetCharAt(i + 1); |
74 | |
75 | if (ch != '"' && ch != '\'') { |
76 | *nextIndex = i + 1; |
77 | return SCE_GD_DEFAULT; |
78 | } |
79 | |
80 | if (ch == chNext && ch == styler.SafeGetCharAt(i + 2)) { |
81 | *nextIndex = i + 3; |
82 | |
83 | if (ch == '"') |
84 | return SCE_GD_TRIPLEDOUBLE; |
85 | else |
86 | return SCE_GD_TRIPLE; |
87 | } else { |
88 | *nextIndex = i + 1; |
89 | |
90 | if (ch == '"') |
91 | return SCE_GD_STRING; |
92 | else |
93 | return SCE_GD_CHARACTER; |
94 | } |
95 | } |
96 | |
97 | inline bool IsAWordChar(int ch, bool unicodeIdentifiers) { |
98 | if (IsASCII(ch)) |
99 | return (IsAlphaNumeric(ch) || ch == '.' || ch == '_'); |
100 | |
101 | if (!unicodeIdentifiers) |
102 | return false; |
103 | |
104 | return IsXidContinue(ch); |
105 | } |
106 | |
107 | inline bool IsAWordStart(int ch, bool unicodeIdentifiers) { |
108 | if (IsASCII(ch)) |
109 | return (IsUpperOrLowerCase(ch) || ch == '_'); |
110 | |
111 | if (!unicodeIdentifiers) |
112 | return false; |
113 | |
114 | return IsXidStart(ch); |
115 | } |
116 | |
117 | bool IsFirstNonWhitespace(Sci_Position pos, Accessor &styler) { |
118 | const Sci_Position line = styler.GetLine(pos); |
119 | const Sci_Position start_pos = styler.LineStart(line); |
120 | for (Sci_Position i = start_pos; i < pos; i++) { |
121 | const char ch = styler[i]; |
122 | if (!(ch == ' ' || ch == '\t')) |
123 | return false; |
124 | } |
125 | return true; |
126 | } |
127 | |
128 | // Options used for LexerGDScript |
129 | struct OptionsGDScript { |
130 | int whingeLevel; |
131 | bool base2or8Literals; |
132 | bool stringsOverNewline; |
133 | bool keywords2NoSubIdentifiers; |
134 | bool fold; |
135 | bool foldQuotes; |
136 | bool foldCompact; |
137 | bool unicodeIdentifiers; |
138 | |
139 | OptionsGDScript() noexcept { |
140 | whingeLevel = 0; |
141 | base2or8Literals = true; |
142 | stringsOverNewline = false; |
143 | keywords2NoSubIdentifiers = false; |
144 | fold = false; |
145 | foldQuotes = false; |
146 | foldCompact = false; |
147 | unicodeIdentifiers = true; |
148 | } |
149 | }; |
150 | |
151 | const char *const gdscriptWordListDesc[] = { |
152 | "Keywords" , |
153 | "Highlighted identifiers" , |
154 | nullptr |
155 | }; |
156 | |
157 | struct OptionSetGDScript : public OptionSet<OptionsGDScript> { |
158 | OptionSetGDScript() { |
159 | DefineProperty("lexer.gdscript.whinge.level" , &OptionsGDScript::whingeLevel, |
160 | "For GDScript code, checks whether indenting is consistent. " |
161 | "The default, 0 turns off indentation checking, " |
162 | "1 checks whether each line is potentially inconsistent with the previous line, " |
163 | "2 checks whether any space characters occur before a tab character in the indentation, " |
164 | "3 checks whether any spaces are in the indentation, and " |
165 | "4 checks for any tab characters in the indentation. " |
166 | "1 is a good level to use." ); |
167 | |
168 | DefineProperty("lexer.gdscript.literals.binary" , &OptionsGDScript::base2or8Literals, |
169 | "Set to 0 to not recognise binary and octal literals: 0b1011 0o712." ); |
170 | |
171 | DefineProperty("lexer.gdscript.strings.over.newline" , &OptionsGDScript::stringsOverNewline, |
172 | "Set to 1 to allow strings to span newline characters." ); |
173 | |
174 | DefineProperty("lexer.gdscript.keywords2.no.sub.identifiers" , &OptionsGDScript::keywords2NoSubIdentifiers, |
175 | "When enabled, it will not style keywords2 items that are used as a sub-identifier. " |
176 | "Example: when set, will not highlight \"foo.open\" when \"open\" is a keywords2 item." ); |
177 | |
178 | DefineProperty("fold" , &OptionsGDScript::fold); |
179 | |
180 | DefineProperty("fold.gdscript.quotes" , &OptionsGDScript::foldQuotes, |
181 | "This option enables folding multi-line quoted strings when using the GDScript lexer." ); |
182 | |
183 | DefineProperty("fold.compact" , &OptionsGDScript::foldCompact); |
184 | |
185 | DefineProperty("lexer.gdscript.unicode.identifiers" , &OptionsGDScript::unicodeIdentifiers, |
186 | "Set to 0 to not recognise Unicode identifiers." ); |
187 | |
188 | DefineWordListSets(gdscriptWordListDesc); |
189 | } |
190 | }; |
191 | |
192 | const char styleSubable[] = { SCE_GD_IDENTIFIER, 0 }; |
193 | |
194 | LexicalClass lexicalClasses[] = { |
195 | // Lexer GDScript SCLEX_GDSCRIPT SCE_GD_: |
196 | 0, "SCE_GD_DEFAULT" , "default" , "White space" , |
197 | 1, "SCE_GD_COMMENTLINE" , "comment line" , "Comment" , |
198 | 2, "SCE_GD_NUMBER" , "literal numeric" , "Number" , |
199 | 3, "SCE_GD_STRING" , "literal string" , "String" , |
200 | 4, "SCE_GD_CHARACTER" , "literal string" , "Single quoted string" , |
201 | 5, "SCE_GD_WORD" , "keyword" , "Keyword" , |
202 | 6, "SCE_GD_TRIPLE" , "literal string" , "Triple quotes" , |
203 | 7, "SCE_GD_TRIPLEDOUBLE" , "literal string" , "Triple double quotes" , |
204 | 8, "SCE_GD_CLASSNAME" , "identifier" , "Class name definition" , |
205 | 9, "SCE_GD_FUNCNAME" , "identifier" , "Function or method name definition" , |
206 | 10, "SCE_GD_OPERATOR" , "operator" , "Operators" , |
207 | 11, "SCE_GD_IDENTIFIER" , "identifier" , "Identifiers" , |
208 | 12, "SCE_GD_COMMENTBLOCK" , "comment" , "Comment-blocks" , |
209 | 13, "SCE_GD_STRINGEOL" , "error literal string" , "End of line where string is not closed" , |
210 | 14, "SCE_GD_WORD2" , "identifier" , "Highlighted identifiers" , |
211 | 15, "SCE_GD_ANNOTATION" , "annotation" , "Annotations" , |
212 | }; |
213 | |
214 | } |
215 | |
216 | class LexerGDScript : public DefaultLexer { |
217 | WordList keywords; |
218 | WordList keywords2; |
219 | OptionsGDScript options; |
220 | OptionSetGDScript osGDScript; |
221 | enum { ssIdentifier }; |
222 | SubStyles subStyles; |
223 | public: |
224 | explicit LexerGDScript() : |
225 | DefaultLexer("gdscript" , SCLEX_GDSCRIPT, lexicalClasses, ELEMENTS(lexicalClasses)), |
226 | subStyles(styleSubable, 0x80, 0x40, 0) { |
227 | } |
228 | ~LexerGDScript() override { |
229 | } |
230 | void SCI_METHOD Release() override { |
231 | delete this; |
232 | } |
233 | int SCI_METHOD Version() const override { |
234 | return lvRelease5; |
235 | } |
236 | const char *SCI_METHOD PropertyNames() override { |
237 | return osGDScript.PropertyNames(); |
238 | } |
239 | int SCI_METHOD PropertyType(const char *name) override { |
240 | return osGDScript.PropertyType(name); |
241 | } |
242 | const char *SCI_METHOD DescribeProperty(const char *name) override { |
243 | return osGDScript.DescribeProperty(name); |
244 | } |
245 | Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override; |
246 | const char * SCI_METHOD PropertyGet(const char *key) override { |
247 | return osGDScript.PropertyGet(key); |
248 | } |
249 | const char *SCI_METHOD DescribeWordListSets() override { |
250 | return osGDScript.DescribeWordListSets(); |
251 | } |
252 | Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override; |
253 | void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override; |
254 | void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override; |
255 | |
256 | void *SCI_METHOD PrivateCall(int, void *) override { |
257 | return nullptr; |
258 | } |
259 | |
260 | int SCI_METHOD LineEndTypesSupported() override { |
261 | return SC_LINE_END_TYPE_UNICODE; |
262 | } |
263 | |
264 | int SCI_METHOD AllocateSubStyles(int styleBase, int numberStyles) override { |
265 | return subStyles.Allocate(styleBase, numberStyles); |
266 | } |
267 | int SCI_METHOD SubStylesStart(int styleBase) override { |
268 | return subStyles.Start(styleBase); |
269 | } |
270 | int SCI_METHOD SubStylesLength(int styleBase) override { |
271 | return subStyles.Length(styleBase); |
272 | } |
273 | int SCI_METHOD StyleFromSubStyle(int subStyle) override { |
274 | const int styleBase = subStyles.BaseStyle(subStyle); |
275 | return styleBase; |
276 | } |
277 | int SCI_METHOD PrimaryStyleFromStyle(int style) override { |
278 | return style; |
279 | } |
280 | void SCI_METHOD FreeSubStyles() override { |
281 | subStyles.Free(); |
282 | } |
283 | void SCI_METHOD SetIdentifiers(int style, const char *identifiers) override { |
284 | subStyles.SetIdentifiers(style, identifiers); |
285 | } |
286 | int SCI_METHOD DistanceToSecondaryStyles() override { |
287 | return 0; |
288 | } |
289 | const char *SCI_METHOD GetSubStyleBases() override { |
290 | return styleSubable; |
291 | } |
292 | |
293 | static ILexer5 *LexerFactoryGDScript() { |
294 | return new LexerGDScript(); |
295 | } |
296 | |
297 | private: |
298 | void ProcessLineEnd(StyleContext &sc, bool &inContinuedString); |
299 | }; |
300 | |
301 | Sci_Position SCI_METHOD LexerGDScript::PropertySet(const char *key, const char *val) { |
302 | if (osGDScript.PropertySet(&options, key, val)) { |
303 | return 0; |
304 | } |
305 | return -1; |
306 | } |
307 | |
308 | Sci_Position SCI_METHOD LexerGDScript::WordListSet(int n, const char *wl) { |
309 | WordList *wordListN = nullptr; |
310 | switch (n) { |
311 | case 0: |
312 | wordListN = &keywords; |
313 | break; |
314 | case 1: |
315 | wordListN = &keywords2; |
316 | break; |
317 | default: |
318 | break; |
319 | } |
320 | Sci_Position firstModification = -1; |
321 | if (wordListN) { |
322 | WordList wlNew; |
323 | wlNew.Set(wl); |
324 | if (*wordListN != wlNew) { |
325 | wordListN->Set(wl); |
326 | firstModification = 0; |
327 | } |
328 | } |
329 | return firstModification; |
330 | } |
331 | |
332 | void LexerGDScript::ProcessLineEnd(StyleContext &sc, bool &inContinuedString) { |
333 | if ((sc.state == SCE_GD_DEFAULT) |
334 | || IsGDTripleQuoteStringState(sc.state)) { |
335 | // Perform colourisation of white space and triple quoted strings at end of each line to allow |
336 | // tab marking to work inside white space and triple quoted strings |
337 | sc.SetState(sc.state); |
338 | } |
339 | |
340 | if (IsGDSingleQuoteStringState(sc.state)) { |
341 | if (inContinuedString || options.stringsOverNewline) { |
342 | inContinuedString = false; |
343 | } else { |
344 | sc.ChangeState(SCE_GD_STRINGEOL); |
345 | sc.ForwardSetState(SCE_GD_DEFAULT); |
346 | } |
347 | } |
348 | } |
349 | |
350 | void SCI_METHOD LexerGDScript::Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) { |
351 | Accessor styler(pAccess, nullptr); |
352 | |
353 | const Sci_Position endPos = startPos + length; |
354 | |
355 | // Backtrack to previous line in case need to fix its tab whinging |
356 | Sci_Position lineCurrent = styler.GetLine(startPos); |
357 | if (startPos > 0) { |
358 | if (lineCurrent > 0) { |
359 | lineCurrent--; |
360 | // Look for backslash-continued lines |
361 | while (lineCurrent > 0) { |
362 | const Sci_Position eolPos = styler.LineStart(lineCurrent) - 1; |
363 | const int eolStyle = styler.StyleAt(eolPos); |
364 | if (eolStyle == SCE_GD_STRING || eolStyle == SCE_GD_CHARACTER |
365 | || eolStyle == SCE_GD_STRINGEOL) { |
366 | lineCurrent -= 1; |
367 | } else { |
368 | break; |
369 | } |
370 | } |
371 | startPos = styler.LineStart(lineCurrent); |
372 | } |
373 | initStyle = startPos == 0 ? SCE_GD_DEFAULT : styler.StyleAt(startPos - 1); |
374 | } |
375 | |
376 | initStyle = initStyle & 31; |
377 | if (initStyle == SCE_GD_STRINGEOL) { |
378 | initStyle = SCE_GD_DEFAULT; |
379 | } |
380 | |
381 | kwType kwLast = kwOther; |
382 | int spaceFlags = 0; |
383 | styler.IndentAmount(lineCurrent, &spaceFlags, IsGDComment); |
384 | bool base_n_number = false; |
385 | |
386 | const WordClassifier &classifierIdentifiers = subStyles.Classifier(SCE_GD_IDENTIFIER); |
387 | |
388 | StyleContext sc(startPos, endPos - startPos, initStyle, styler); |
389 | |
390 | bool indentGood = true; |
391 | Sci_Position startIndicator = sc.currentPos; |
392 | bool inContinuedString = false; |
393 | |
394 | for (; sc.More(); sc.Forward()) { |
395 | |
396 | if (sc.atLineStart) { |
397 | styler.IndentAmount(lineCurrent, &spaceFlags, IsGDComment); |
398 | indentGood = true; |
399 | if (options.whingeLevel == 1) { |
400 | indentGood = (spaceFlags & wsInconsistent) == 0; |
401 | } else if (options.whingeLevel == 2) { |
402 | indentGood = (spaceFlags & wsSpaceTab) == 0; |
403 | } else if (options.whingeLevel == 3) { |
404 | indentGood = (spaceFlags & wsSpace) == 0; |
405 | } else if (options.whingeLevel == 4) { |
406 | indentGood = (spaceFlags & wsTab) == 0; |
407 | } |
408 | if (!indentGood) { |
409 | styler.IndicatorFill(startIndicator, sc.currentPos, indicatorWhitespace, 0); |
410 | startIndicator = sc.currentPos; |
411 | } |
412 | } |
413 | |
414 | if (sc.atLineEnd) { |
415 | ProcessLineEnd(sc, inContinuedString); |
416 | lineCurrent++; |
417 | if (!sc.More()) |
418 | break; |
419 | } |
420 | |
421 | bool needEOLCheck = false; |
422 | |
423 | if (sc.state == SCE_GD_OPERATOR) { |
424 | kwLast = kwOther; |
425 | sc.SetState(SCE_GD_DEFAULT); |
426 | } else if (sc.state == SCE_GD_NUMBER) { |
427 | if (!IsAWordChar(sc.ch, false) && |
428 | !(!base_n_number && ((sc.ch == '+' || sc.ch == '-') && (sc.chPrev == 'e' || sc.chPrev == 'E')))) { |
429 | sc.SetState(SCE_GD_DEFAULT); |
430 | } |
431 | } else if (sc.state == SCE_GD_IDENTIFIER) { |
432 | if ((sc.ch == '.') || (!IsAWordChar(sc.ch, options.unicodeIdentifiers))) { |
433 | char s[100]; |
434 | sc.GetCurrent(s, sizeof(s)); |
435 | int style = SCE_GD_IDENTIFIER; |
436 | if (keywords.InList(s)) { |
437 | style = SCE_GD_WORD; |
438 | } else if (kwLast == kwClass) { |
439 | style = SCE_GD_CLASSNAME; |
440 | } else if (kwLast == kwDef) { |
441 | style = SCE_GD_FUNCNAME; |
442 | } else if (keywords2.InList(s)) { |
443 | if (options.keywords2NoSubIdentifiers) { |
444 | // We don't want to highlight keywords2 |
445 | // that are used as a sub-identifier, |
446 | // i.e. not open in "foo.open". |
447 | const Sci_Position pos = styler.GetStartSegment() - 1; |
448 | if (pos < 0 || (styler.SafeGetCharAt(pos, '\0') != '.')) |
449 | style = SCE_GD_WORD2; |
450 | } else { |
451 | style = SCE_GD_WORD2; |
452 | } |
453 | } else { |
454 | const int subStyle = classifierIdentifiers.ValueFor(s); |
455 | if (subStyle >= 0) { |
456 | style = subStyle; |
457 | } |
458 | } |
459 | sc.ChangeState(style); |
460 | sc.SetState(SCE_GD_DEFAULT); |
461 | if (style == SCE_GD_WORD) { |
462 | if (0 == strcmp(s, "class" )) |
463 | kwLast = kwClass; |
464 | else if (0 == strcmp(s, "func" )) |
465 | kwLast = kwDef; |
466 | else if (0 == strcmp(s, "extends" )) |
467 | kwLast = kwExtends; |
468 | else |
469 | kwLast = kwOther; |
470 | } else { |
471 | kwLast = kwOther; |
472 | } |
473 | } |
474 | } else if ((sc.state == SCE_GD_COMMENTLINE) || (sc.state == SCE_GD_COMMENTBLOCK)) { |
475 | if (sc.ch == '\r' || sc.ch == '\n') { |
476 | sc.SetState(SCE_GD_DEFAULT); |
477 | } |
478 | } else if (sc.state == SCE_GD_ANNOTATION) { |
479 | if (!IsAWordStart(sc.ch, options.unicodeIdentifiers)) { |
480 | sc.SetState(SCE_GD_DEFAULT); |
481 | } |
482 | } else if (IsGDSingleQuoteStringState(sc.state)) { |
483 | if (sc.ch == '\\') { |
484 | if ((sc.chNext == '\r') && (sc.GetRelative(2) == '\n')) { |
485 | sc.Forward(); |
486 | } |
487 | if (sc.chNext == '\n' || sc.chNext == '\r') { |
488 | inContinuedString = true; |
489 | } else { |
490 | // Don't roll over the newline. |
491 | sc.Forward(); |
492 | } |
493 | } else if (sc.ch == GetGDStringQuoteChar(sc.state)) { |
494 | sc.ForwardSetState(SCE_GD_DEFAULT); |
495 | needEOLCheck = true; |
496 | } |
497 | } else if (sc.state == SCE_GD_TRIPLE) { |
498 | if (sc.ch == '\\') { |
499 | sc.Forward(); |
500 | } else if (sc.Match(R"(''')" )) { |
501 | sc.Forward(); |
502 | sc.Forward(); |
503 | sc.ForwardSetState(SCE_GD_DEFAULT); |
504 | needEOLCheck = true; |
505 | } |
506 | } else if (sc.state == SCE_GD_TRIPLEDOUBLE) { |
507 | if (sc.ch == '\\') { |
508 | sc.Forward(); |
509 | } else if (sc.Match(R"(""")" )) { |
510 | sc.Forward(); |
511 | sc.Forward(); |
512 | sc.ForwardSetState(SCE_GD_DEFAULT); |
513 | needEOLCheck = true; |
514 | } |
515 | } |
516 | |
517 | if (!indentGood && !IsASpaceOrTab(sc.ch)) { |
518 | styler.IndicatorFill(startIndicator, sc.currentPos, indicatorWhitespace, 1); |
519 | startIndicator = sc.currentPos; |
520 | indentGood = true; |
521 | } |
522 | |
523 | // State exit code may have moved on to end of line |
524 | if (needEOLCheck && sc.atLineEnd) { |
525 | ProcessLineEnd(sc, inContinuedString); |
526 | lineCurrent++; |
527 | styler.IndentAmount(lineCurrent, &spaceFlags, IsGDComment); |
528 | if (!sc.More()) |
529 | break; |
530 | } |
531 | |
532 | // Check for a new state starting character |
533 | if (sc.state == SCE_GD_DEFAULT) { |
534 | if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) { |
535 | if (sc.ch == '0' && (sc.chNext == 'x' || sc.chNext == 'X')) { |
536 | base_n_number = true; |
537 | sc.SetState(SCE_GD_NUMBER); |
538 | } else if (sc.ch == '0' && |
539 | (sc.chNext == 'o' || sc.chNext == 'O' || sc.chNext == 'b' || sc.chNext == 'B')) { |
540 | if (options.base2or8Literals) { |
541 | base_n_number = true; |
542 | sc.SetState(SCE_GD_NUMBER); |
543 | } else { |
544 | sc.SetState(SCE_GD_NUMBER); |
545 | sc.ForwardSetState(SCE_GD_IDENTIFIER); |
546 | } |
547 | } else { |
548 | base_n_number = false; |
549 | sc.SetState(SCE_GD_NUMBER); |
550 | } |
551 | } else if (isoperator(sc.ch) || sc.ch == '`') { |
552 | sc.SetState(SCE_GD_OPERATOR); |
553 | } else if (sc.ch == '#') { |
554 | sc.SetState(sc.chNext == '#' ? SCE_GD_COMMENTBLOCK : SCE_GD_COMMENTLINE); |
555 | } else if (sc.ch == '@') { |
556 | if (IsFirstNonWhitespace(sc.currentPos, styler)) |
557 | sc.SetState(SCE_GD_ANNOTATION); |
558 | else |
559 | sc.SetState(SCE_GD_OPERATOR); |
560 | } else if (IsGDStringStart(sc.ch)) { |
561 | Sci_PositionU nextIndex = 0; |
562 | sc.SetState(GetGDStringState(styler, sc.currentPos, &nextIndex)); |
563 | while (nextIndex > (sc.currentPos + 1) && sc.More()) { |
564 | sc.Forward(); |
565 | } |
566 | } else if (IsAWordStart(sc.ch, options.unicodeIdentifiers)) { |
567 | sc.SetState(SCE_GD_IDENTIFIER); |
568 | } |
569 | } |
570 | } |
571 | styler.IndicatorFill(startIndicator, sc.currentPos, indicatorWhitespace, 0); |
572 | sc.Complete(); |
573 | } |
574 | |
575 | static bool (Sci_Position line, Accessor &styler) { |
576 | const Sci_Position pos = styler.LineStart(line); |
577 | const Sci_Position eol_pos = styler.LineStart(line + 1) - 1; |
578 | for (Sci_Position i = pos; i < eol_pos; i++) { |
579 | const char ch = styler[i]; |
580 | if (ch == '#') |
581 | return true; |
582 | else if (ch != ' ' && ch != '\t') |
583 | return false; |
584 | } |
585 | return false; |
586 | } |
587 | |
588 | static bool IsQuoteLine(Sci_Position line, const Accessor &styler) { |
589 | const int style = styler.StyleAt(styler.LineStart(line)) & 31; |
590 | return IsGDTripleQuoteStringState(style); |
591 | } |
592 | |
593 | |
594 | void SCI_METHOD LexerGDScript::Fold(Sci_PositionU startPos, Sci_Position length, int /*initStyle - unused*/, IDocument *pAccess) { |
595 | if (!options.fold) |
596 | return; |
597 | |
598 | Accessor styler(pAccess, nullptr); |
599 | |
600 | const Sci_Position maxPos = startPos + length; |
601 | const Sci_Position maxLines = (maxPos == styler.Length()) ? styler.GetLine(maxPos) : styler.GetLine(maxPos - 1); // Requested last line |
602 | const Sci_Position docLines = styler.GetLine(styler.Length()); // Available last line |
603 | |
604 | // Backtrack to previous non-blank line so we can determine indent level |
605 | // for any white space lines (needed esp. within triple quoted strings) |
606 | // and so we can fix any preceding fold level (which is why we go back |
607 | // at least one line in all cases) |
608 | int spaceFlags = 0; |
609 | Sci_Position lineCurrent = styler.GetLine(startPos); |
610 | int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, nullptr); |
611 | while (lineCurrent > 0) { |
612 | lineCurrent--; |
613 | indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, nullptr); |
614 | if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG) && |
615 | (!IsCommentLine(lineCurrent, styler)) && |
616 | (!IsQuoteLine(lineCurrent, styler))) |
617 | break; |
618 | } |
619 | int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK; |
620 | |
621 | // Set up initial loop state |
622 | startPos = styler.LineStart(lineCurrent); |
623 | int prev_state = SCE_GD_DEFAULT & 31; |
624 | if (lineCurrent >= 1) |
625 | prev_state = styler.StyleAt(startPos - 1) & 31; |
626 | int prevQuote = options.foldQuotes && IsGDTripleQuoteStringState(prev_state); |
627 | |
628 | // Process all characters to end of requested range or end of any triple quote |
629 | //that hangs over the end of the range. Cap processing in all cases |
630 | // to end of document (in case of unclosed quote at end). |
631 | while ((lineCurrent <= docLines) && ((lineCurrent <= maxLines) || prevQuote)) { |
632 | |
633 | // Gather info |
634 | int lev = indentCurrent; |
635 | Sci_Position lineNext = lineCurrent + 1; |
636 | int indentNext = indentCurrent; |
637 | int quote = false; |
638 | if (lineNext <= docLines) { |
639 | // Information about next line is only available if not at end of document |
640 | indentNext = styler.IndentAmount(lineNext, &spaceFlags, nullptr); |
641 | const Sci_Position lookAtPos = (styler.LineStart(lineNext) == styler.Length()) ? styler.Length() - 1 : styler.LineStart(lineNext); |
642 | const int style = styler.StyleAt(lookAtPos) & 31; |
643 | quote = options.foldQuotes && IsGDTripleQuoteStringState(style); |
644 | } |
645 | const bool quote_start = (quote && !prevQuote); |
646 | const bool quote_continue = (quote && prevQuote); |
647 | if (!quote || !prevQuote) |
648 | indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK; |
649 | if (quote) |
650 | indentNext = indentCurrentLevel; |
651 | if (indentNext & SC_FOLDLEVELWHITEFLAG) |
652 | indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel; |
653 | |
654 | if (quote_start) { |
655 | // Place fold point at start of triple quoted string |
656 | lev |= SC_FOLDLEVELHEADERFLAG; |
657 | } else if (quote_continue || prevQuote) { |
658 | // Add level to rest of lines in the string |
659 | lev = lev + 1; |
660 | } |
661 | |
662 | // Skip past any blank lines for next indent level info; we skip also |
663 | // comments (all comments, not just those starting in column 0) |
664 | // which effectively folds them into surrounding code rather |
665 | // than screwing up folding. If comments end file, use the min |
666 | // comment indent as the level after |
667 | |
668 | int = indentCurrentLevel; |
669 | while (!quote && |
670 | (lineNext < docLines) && |
671 | ((indentNext & SC_FOLDLEVELWHITEFLAG) || (IsCommentLine(lineNext, styler)))) { |
672 | |
673 | if (IsCommentLine(lineNext, styler) && indentNext < minCommentLevel) { |
674 | minCommentLevel = indentNext; |
675 | } |
676 | |
677 | lineNext++; |
678 | indentNext = styler.IndentAmount(lineNext, &spaceFlags, nullptr); |
679 | } |
680 | |
681 | const int = ((lineNext < docLines) ? indentNext & SC_FOLDLEVELNUMBERMASK : minCommentLevel); |
682 | const int = std::max(indentCurrentLevel, levelAfterComments); |
683 | |
684 | // Now set all the indent levels on the lines we skipped |
685 | // Do this from end to start. Once we encounter one line |
686 | // which is indented more than the line after the end of |
687 | // the comment-block, use the level of the block before |
688 | |
689 | Sci_Position skipLine = lineNext; |
690 | int skipLevel = levelAfterComments; |
691 | |
692 | while (--skipLine > lineCurrent) { |
693 | const int skipLineIndent = styler.IndentAmount(skipLine, &spaceFlags, nullptr); |
694 | |
695 | if (options.foldCompact) { |
696 | if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments) |
697 | skipLevel = levelBeforeComments; |
698 | |
699 | const int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG; |
700 | |
701 | styler.SetLevel(skipLine, skipLevel | whiteFlag); |
702 | } else { |
703 | if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments && |
704 | !(skipLineIndent & SC_FOLDLEVELWHITEFLAG) && |
705 | !IsCommentLine(skipLine, styler)) |
706 | skipLevel = levelBeforeComments; |
707 | |
708 | styler.SetLevel(skipLine, skipLevel); |
709 | } |
710 | } |
711 | |
712 | // Set fold header on non-quote line |
713 | if (!quote && !(indentCurrent & SC_FOLDLEVELWHITEFLAG)) { |
714 | if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK)) |
715 | lev |= SC_FOLDLEVELHEADERFLAG; |
716 | } |
717 | |
718 | // Keep track of triple quote state of previous line |
719 | prevQuote = quote; |
720 | |
721 | // Set fold level for this line and move to next line |
722 | styler.SetLevel(lineCurrent, options.foldCompact ? lev : lev & ~SC_FOLDLEVELWHITEFLAG); |
723 | indentCurrent = indentNext; |
724 | lineCurrent = lineNext; |
725 | } |
726 | |
727 | // NOTE: Cannot set level of last line here because indentCurrent doesn't have |
728 | // header flag set; the loop above is crafted to take care of this case! |
729 | //styler.SetLevel(lineCurrent, indentCurrent); |
730 | } |
731 | |
732 | LexerModule lmGDScript(SCLEX_GDSCRIPT, LexerGDScript::LexerFactoryGDScript, "gdscript" , |
733 | gdscriptWordListDesc); |
734 | |