| 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 | |