| 1 | // Scintilla source code edit control |
| 2 | /** @file LexPascal.cxx |
| 3 | ** Lexer for Pascal. |
| 4 | ** Written by Laurent le Tynevez |
| 5 | ** Updated by Simon Steele <s.steele@pnotepad.org> September 2002 |
| 6 | ** Updated by Mathias Rauen <scite@madshi.net> May 2003 (Delphi adjustments) |
| 7 | ** Completely rewritten by Marko Njezic <sf@maxempire.com> October 2008 |
| 8 | **/ |
| 9 | |
| 10 | /* |
| 11 | |
| 12 | A few words about features of the new completely rewritten LexPascal... |
| 13 | |
| 14 | Generally speaking LexPascal tries to support all available Delphi features (up |
| 15 | to Delphi XE4 at this time). |
| 16 | |
| 17 | ~ HIGHLIGHTING: |
| 18 | |
| 19 | If you enable "lexer.pascal.smart.highlighting" property, some keywords will |
| 20 | only be highlighted in appropriate context. As implemented those are keywords |
| 21 | related to property and DLL exports declarations (similar to how Delphi IDE |
| 22 | works). |
| 23 | |
| 24 | For example, keywords "read" and "write" will only be highlighted if they are in |
| 25 | property declaration: |
| 26 | |
| 27 | property MyProperty: boolean read FMyProperty write FMyProperty; |
| 28 | |
| 29 | ~ FOLDING: |
| 30 | |
| 31 | Folding is supported in the following cases: |
| 32 | |
| 33 | - Folding of stream-like comments |
| 34 | - Folding of groups of consecutive line comments |
| 35 | - Folding of preprocessor blocks (the following preprocessor blocks are |
| 36 | supported: IF / IFEND; IFDEF, IFNDEF, IFOPT / ENDIF and REGION / ENDREGION |
| 37 | blocks), including nesting of preprocessor blocks up to 255 levels |
| 38 | - Folding of code blocks on appropriate keywords (the following code blocks are |
| 39 | supported: "begin, asm, record, try, case / end" blocks, class & object |
| 40 | declarations and interface declarations) |
| 41 | |
| 42 | Remarks: |
| 43 | |
| 44 | - Folding of code blocks tries to handle all special cases in which folding |
| 45 | should not occur. As implemented those are: |
| 46 | |
| 47 | 1. Structure "record case / end" (there's only one "end" statement and "case" is |
| 48 | ignored as fold point) |
| 49 | 2. Forward class declarations ("type TMyClass = class;") and object method |
| 50 | declarations ("TNotifyEvent = procedure(Sender: TObject) of object;") are |
| 51 | ignored as fold points |
| 52 | 3. Simplified complete class declarations ("type TMyClass = class(TObject);") |
| 53 | are ignored as fold points |
| 54 | 4. Every other situation when class keyword doesn't actually start class |
| 55 | declaration ("class procedure", "class function", "class of", "class var", |
| 56 | "class property" and "class operator") |
| 57 | 5. Forward (disp)interface declarations ("type IMyInterface = interface;") are |
| 58 | ignored as fold points |
| 59 | |
| 60 | - Folding of code blocks inside preprocessor blocks is disabled (any comments |
| 61 | inside them will be folded fine) because there is no guarantee that complete |
| 62 | code block will be contained inside folded preprocessor block in which case |
| 63 | folded code block could end prematurely at the end of preprocessor block if |
| 64 | there is no closing statement inside. This was done in order to properly process |
| 65 | document that may contain something like this: |
| 66 | |
| 67 | type |
| 68 | {$IFDEF UNICODE} |
| 69 | TMyClass = class(UnicodeAncestor) |
| 70 | {$ELSE} |
| 71 | TMyClass = class(AnsiAncestor) |
| 72 | {$ENDIF} |
| 73 | private |
| 74 | ... |
| 75 | public |
| 76 | ... |
| 77 | published |
| 78 | ... |
| 79 | end; |
| 80 | |
| 81 | If class declarations were folded, then the second class declaration would end |
| 82 | at "$ENDIF" statement, first class statement would end at "end;" statement and |
| 83 | preprocessor "$IFDEF" block would go all the way to the end of document. |
| 84 | However, having in mind all this, if you want to enable folding of code blocks |
| 85 | inside preprocessor blocks, you can disable folding of preprocessor blocks by |
| 86 | changing "fold.preprocessor" property, in which case everything inside them |
| 87 | would be folded. |
| 88 | |
| 89 | ~ KEYWORDS: |
| 90 | |
| 91 | The list of keywords that can be used in pascal.properties file (up to Delphi |
| 92 | XE4): |
| 93 | |
| 94 | - Keywords: absolute abstract and array as asm assembler automated begin case |
| 95 | cdecl class const constructor delayed deprecated destructor dispid dispinterface |
| 96 | div do downto dynamic else end except experimental export exports external far |
| 97 | file final finalization finally for forward function goto helper if |
| 98 | implementation in inherited initialization inline interface is label library |
| 99 | message mod near nil not object of on operator or out overload override packed |
| 100 | pascal platform private procedure program property protected public published |
| 101 | raise record reference register reintroduce repeat resourcestring safecall |
| 102 | sealed set shl shr static stdcall strict string then threadvar to try type unit |
| 103 | unsafe until uses var varargs virtual while winapi with xor |
| 104 | |
| 105 | - Keywords related to the "smart highlithing" feature: add default implements |
| 106 | index name nodefault read readonly remove stored write writeonly |
| 107 | |
| 108 | - Keywords related to Delphi packages (in addition to all above): package |
| 109 | contains requires |
| 110 | |
| 111 | */ |
| 112 | |
| 113 | #include <stdlib.h> |
| 114 | #include <string.h> |
| 115 | #include <stdio.h> |
| 116 | #include <stdarg.h> |
| 117 | #include <assert.h> |
| 118 | #include <ctype.h> |
| 119 | |
| 120 | #include <string> |
| 121 | #include <string_view> |
| 122 | |
| 123 | #include "ILexer.h" |
| 124 | #include "Scintilla.h" |
| 125 | #include "SciLexer.h" |
| 126 | |
| 127 | #include "WordList.h" |
| 128 | #include "LexAccessor.h" |
| 129 | #include "Accessor.h" |
| 130 | #include "StyleContext.h" |
| 131 | #include "CharacterSet.h" |
| 132 | #include "LexerModule.h" |
| 133 | |
| 134 | using namespace Lexilla; |
| 135 | |
| 136 | static void GetRangeLowered(Sci_PositionU start, |
| 137 | Sci_PositionU end, |
| 138 | Accessor &styler, |
| 139 | char *s, |
| 140 | Sci_PositionU len) { |
| 141 | Sci_PositionU i = 0; |
| 142 | while ((i < end - start + 1) && (i < len-1)) { |
| 143 | s[i] = static_cast<char>(tolower(styler[start + i])); |
| 144 | i++; |
| 145 | } |
| 146 | s[i] = '\0'; |
| 147 | } |
| 148 | |
| 149 | static void GetForwardRangeLowered(Sci_PositionU start, |
| 150 | CharacterSet &charSet, |
| 151 | Accessor &styler, |
| 152 | char *s, |
| 153 | Sci_PositionU len) { |
| 154 | Sci_PositionU i = 0; |
| 155 | while ((i < len-1) && charSet.Contains(styler.SafeGetCharAt(start + i))) { |
| 156 | s[i] = static_cast<char>(tolower(styler.SafeGetCharAt(start + i))); |
| 157 | i++; |
| 158 | } |
| 159 | s[i] = '\0'; |
| 160 | |
| 161 | } |
| 162 | |
| 163 | enum { |
| 164 | stateInAsm = 0x1000, |
| 165 | stateInProperty = 0x2000, |
| 166 | stateInExport = 0x4000, |
| 167 | stateFoldInPreprocessor = 0x0100, |
| 168 | stateFoldInRecord = 0x0200, |
| 169 | stateFoldInPreprocessorLevelMask = 0x00FF, |
| 170 | stateFoldMaskAll = 0x0FFF |
| 171 | }; |
| 172 | |
| 173 | static void ClassifyPascalWord(WordList *keywordlists[], StyleContext &sc, int &curLineState, bool bSmartHighlighting) { |
| 174 | WordList& keywords = *keywordlists[0]; |
| 175 | |
| 176 | char s[100]; |
| 177 | sc.GetCurrentLowered(s, sizeof(s)); |
| 178 | if (keywords.InList(s)) { |
| 179 | if (curLineState & stateInAsm) { |
| 180 | if (strcmp(s, "end" ) == 0 && sc.GetRelative(-4) != '@') { |
| 181 | curLineState &= ~stateInAsm; |
| 182 | sc.ChangeState(SCE_PAS_WORD); |
| 183 | } else { |
| 184 | sc.ChangeState(SCE_PAS_ASM); |
| 185 | } |
| 186 | } else { |
| 187 | bool ignoreKeyword = false; |
| 188 | if (strcmp(s, "asm" ) == 0) { |
| 189 | curLineState |= stateInAsm; |
| 190 | } else if (bSmartHighlighting) { |
| 191 | if (strcmp(s, "property" ) == 0) { |
| 192 | curLineState |= stateInProperty; |
| 193 | } else if (strcmp(s, "exports" ) == 0) { |
| 194 | curLineState |= stateInExport; |
| 195 | } else if (!(curLineState & (stateInProperty | stateInExport)) && strcmp(s, "index" ) == 0) { |
| 196 | ignoreKeyword = true; |
| 197 | } else if (!(curLineState & stateInExport) && strcmp(s, "name" ) == 0) { |
| 198 | ignoreKeyword = true; |
| 199 | } else if (!(curLineState & stateInProperty) && |
| 200 | (strcmp(s, "read" ) == 0 || strcmp(s, "write" ) == 0 || |
| 201 | strcmp(s, "default" ) == 0 || strcmp(s, "nodefault" ) == 0 || |
| 202 | strcmp(s, "stored" ) == 0 || strcmp(s, "implements" ) == 0 || |
| 203 | strcmp(s, "readonly" ) == 0 || strcmp(s, "writeonly" ) == 0 || |
| 204 | strcmp(s, "add" ) == 0 || strcmp(s, "remove" ) == 0)) { |
| 205 | ignoreKeyword = true; |
| 206 | } |
| 207 | } |
| 208 | if (!ignoreKeyword) { |
| 209 | sc.ChangeState(SCE_PAS_WORD); |
| 210 | } |
| 211 | } |
| 212 | } else if (curLineState & stateInAsm) { |
| 213 | sc.ChangeState(SCE_PAS_ASM); |
| 214 | } |
| 215 | sc.SetState(SCE_PAS_DEFAULT); |
| 216 | } |
| 217 | |
| 218 | static void ColourisePascalDoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *keywordlists[], |
| 219 | Accessor &styler) { |
| 220 | bool bSmartHighlighting = styler.GetPropertyInt("lexer.pascal.smart.highlighting" , 1) != 0; |
| 221 | |
| 222 | CharacterSet setWordStart(CharacterSet::setAlpha, "_" , 0x80, true); |
| 223 | CharacterSet setWord(CharacterSet::setAlphaNum, "_" , 0x80, true); |
| 224 | CharacterSet setNumber(CharacterSet::setDigits, ".-+eE" ); |
| 225 | CharacterSet setHexNumber(CharacterSet::setDigits, "abcdefABCDEF" ); |
| 226 | CharacterSet setOperator(CharacterSet::setNone, "#$&'()*+,-./:;<=>@[]^{}" ); |
| 227 | |
| 228 | Sci_Position curLine = styler.GetLine(startPos); |
| 229 | int curLineState = curLine > 0 ? styler.GetLineState(curLine - 1) : 0; |
| 230 | |
| 231 | StyleContext sc(startPos, length, initStyle, styler); |
| 232 | |
| 233 | for (; sc.More(); sc.Forward()) { |
| 234 | if (sc.atLineEnd) { |
| 235 | // Update the line state, so it can be seen by next line |
| 236 | curLine = styler.GetLine(sc.currentPos); |
| 237 | styler.SetLineState(curLine, curLineState); |
| 238 | } |
| 239 | |
| 240 | // Determine if the current state should terminate. |
| 241 | switch (sc.state) { |
| 242 | case SCE_PAS_NUMBER: |
| 243 | if (!setNumber.Contains(sc.ch) || (sc.ch == '.' && sc.chNext == '.')) { |
| 244 | sc.SetState(SCE_PAS_DEFAULT); |
| 245 | } else if (sc.ch == '-' || sc.ch == '+') { |
| 246 | if (sc.chPrev != 'E' && sc.chPrev != 'e') { |
| 247 | sc.SetState(SCE_PAS_DEFAULT); |
| 248 | } |
| 249 | } |
| 250 | break; |
| 251 | case SCE_PAS_IDENTIFIER: |
| 252 | if (!setWord.Contains(sc.ch)) { |
| 253 | ClassifyPascalWord(keywordlists, sc, curLineState, bSmartHighlighting); |
| 254 | } |
| 255 | break; |
| 256 | case SCE_PAS_HEXNUMBER: |
| 257 | if (!setHexNumber.Contains(sc.ch)) { |
| 258 | sc.SetState(SCE_PAS_DEFAULT); |
| 259 | } |
| 260 | break; |
| 261 | case SCE_PAS_COMMENT: |
| 262 | case SCE_PAS_PREPROCESSOR: |
| 263 | if (sc.ch == '}') { |
| 264 | sc.ForwardSetState(SCE_PAS_DEFAULT); |
| 265 | } |
| 266 | break; |
| 267 | case SCE_PAS_COMMENT2: |
| 268 | case SCE_PAS_PREPROCESSOR2: |
| 269 | if (sc.Match('*', ')')) { |
| 270 | sc.Forward(); |
| 271 | sc.ForwardSetState(SCE_PAS_DEFAULT); |
| 272 | } |
| 273 | break; |
| 274 | case SCE_PAS_COMMENTLINE: |
| 275 | if (sc.atLineStart) { |
| 276 | sc.SetState(SCE_PAS_DEFAULT); |
| 277 | } |
| 278 | break; |
| 279 | case SCE_PAS_STRING: |
| 280 | if (sc.atLineEnd) { |
| 281 | sc.ChangeState(SCE_PAS_STRINGEOL); |
| 282 | } else if (sc.ch == '\'' && sc.chNext == '\'') { |
| 283 | sc.Forward(); |
| 284 | } else if (sc.ch == '\'') { |
| 285 | sc.ForwardSetState(SCE_PAS_DEFAULT); |
| 286 | } |
| 287 | break; |
| 288 | case SCE_PAS_STRINGEOL: |
| 289 | if (sc.atLineStart) { |
| 290 | sc.SetState(SCE_PAS_DEFAULT); |
| 291 | } |
| 292 | break; |
| 293 | case SCE_PAS_CHARACTER: |
| 294 | if (!setHexNumber.Contains(sc.ch) && sc.ch != '$') { |
| 295 | sc.SetState(SCE_PAS_DEFAULT); |
| 296 | } |
| 297 | break; |
| 298 | case SCE_PAS_OPERATOR: |
| 299 | if (bSmartHighlighting && sc.chPrev == ';') { |
| 300 | curLineState &= ~(stateInProperty | stateInExport); |
| 301 | } |
| 302 | sc.SetState(SCE_PAS_DEFAULT); |
| 303 | break; |
| 304 | case SCE_PAS_ASM: |
| 305 | sc.SetState(SCE_PAS_DEFAULT); |
| 306 | break; |
| 307 | } |
| 308 | |
| 309 | // Determine if a new state should be entered. |
| 310 | if (sc.state == SCE_PAS_DEFAULT) { |
| 311 | if (IsADigit(sc.ch) && !(curLineState & stateInAsm)) { |
| 312 | sc.SetState(SCE_PAS_NUMBER); |
| 313 | } else if (setWordStart.Contains(sc.ch)) { |
| 314 | sc.SetState(SCE_PAS_IDENTIFIER); |
| 315 | } else if (sc.ch == '$' && !(curLineState & stateInAsm)) { |
| 316 | sc.SetState(SCE_PAS_HEXNUMBER); |
| 317 | } else if (sc.Match('{', '$')) { |
| 318 | sc.SetState(SCE_PAS_PREPROCESSOR); |
| 319 | } else if (sc.ch == '{') { |
| 320 | sc.SetState(SCE_PAS_COMMENT); |
| 321 | } else if (sc.Match("(*$" )) { |
| 322 | sc.SetState(SCE_PAS_PREPROCESSOR2); |
| 323 | } else if (sc.Match('(', '*')) { |
| 324 | sc.SetState(SCE_PAS_COMMENT2); |
| 325 | sc.Forward(); // Eat the * so it isn't used for the end of the comment |
| 326 | } else if (sc.Match('/', '/')) { |
| 327 | sc.SetState(SCE_PAS_COMMENTLINE); |
| 328 | } else if (sc.ch == '\'') { |
| 329 | sc.SetState(SCE_PAS_STRING); |
| 330 | } else if (sc.ch == '#') { |
| 331 | sc.SetState(SCE_PAS_CHARACTER); |
| 332 | } else if (setOperator.Contains(sc.ch) && !(curLineState & stateInAsm)) { |
| 333 | sc.SetState(SCE_PAS_OPERATOR); |
| 334 | } else if (curLineState & stateInAsm) { |
| 335 | sc.SetState(SCE_PAS_ASM); |
| 336 | } |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | if (sc.state == SCE_PAS_IDENTIFIER && setWord.Contains(sc.chPrev)) { |
| 341 | ClassifyPascalWord(keywordlists, sc, curLineState, bSmartHighlighting); |
| 342 | } |
| 343 | |
| 344 | sc.Complete(); |
| 345 | } |
| 346 | |
| 347 | static bool (int style) { |
| 348 | return style == SCE_PAS_COMMENT || style == SCE_PAS_COMMENT2; |
| 349 | } |
| 350 | |
| 351 | static bool (Sci_Position line, Accessor &styler) { |
| 352 | Sci_Position pos = styler.LineStart(line); |
| 353 | Sci_Position eolPos = styler.LineStart(line + 1) - 1; |
| 354 | for (Sci_Position i = pos; i < eolPos; i++) { |
| 355 | char ch = styler[i]; |
| 356 | char chNext = styler.SafeGetCharAt(i + 1); |
| 357 | int style = styler.StyleAt(i); |
| 358 | if (ch == '/' && chNext == '/' && style == SCE_PAS_COMMENTLINE) { |
| 359 | return true; |
| 360 | } else if (!IsASpaceOrTab(ch)) { |
| 361 | return false; |
| 362 | } |
| 363 | } |
| 364 | return false; |
| 365 | } |
| 366 | |
| 367 | static unsigned int GetFoldInPreprocessorLevelFlag(int lineFoldStateCurrent) { |
| 368 | return lineFoldStateCurrent & stateFoldInPreprocessorLevelMask; |
| 369 | } |
| 370 | |
| 371 | static void SetFoldInPreprocessorLevelFlag(int &lineFoldStateCurrent, unsigned int nestLevel) { |
| 372 | lineFoldStateCurrent &= ~stateFoldInPreprocessorLevelMask; |
| 373 | lineFoldStateCurrent |= nestLevel & stateFoldInPreprocessorLevelMask; |
| 374 | } |
| 375 | |
| 376 | static void ClassifyPascalPreprocessorFoldPoint(int &levelCurrent, int &lineFoldStateCurrent, |
| 377 | Sci_PositionU startPos, Accessor &styler) { |
| 378 | CharacterSet setWord(CharacterSet::setAlpha); |
| 379 | |
| 380 | char s[11]; // Size of the longest possible keyword + one additional character + null |
| 381 | GetForwardRangeLowered(startPos, setWord, styler, s, sizeof(s)); |
| 382 | |
| 383 | unsigned int nestLevel = GetFoldInPreprocessorLevelFlag(lineFoldStateCurrent); |
| 384 | |
| 385 | if (strcmp(s, "if" ) == 0 || |
| 386 | strcmp(s, "ifdef" ) == 0 || |
| 387 | strcmp(s, "ifndef" ) == 0 || |
| 388 | strcmp(s, "ifopt" ) == 0 || |
| 389 | strcmp(s, "region" ) == 0) { |
| 390 | nestLevel++; |
| 391 | SetFoldInPreprocessorLevelFlag(lineFoldStateCurrent, nestLevel); |
| 392 | lineFoldStateCurrent |= stateFoldInPreprocessor; |
| 393 | levelCurrent++; |
| 394 | } else if (strcmp(s, "endif" ) == 0 || |
| 395 | strcmp(s, "ifend" ) == 0 || |
| 396 | strcmp(s, "endregion" ) == 0) { |
| 397 | nestLevel--; |
| 398 | SetFoldInPreprocessorLevelFlag(lineFoldStateCurrent, nestLevel); |
| 399 | if (nestLevel == 0) { |
| 400 | lineFoldStateCurrent &= ~stateFoldInPreprocessor; |
| 401 | } |
| 402 | levelCurrent--; |
| 403 | if (levelCurrent < SC_FOLDLEVELBASE) { |
| 404 | levelCurrent = SC_FOLDLEVELBASE; |
| 405 | } |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | static Sci_PositionU SkipWhiteSpace(Sci_PositionU currentPos, Sci_PositionU endPos, |
| 410 | Accessor &styler, bool includeChars = false) { |
| 411 | CharacterSet setWord(CharacterSet::setAlphaNum, "_" ); |
| 412 | Sci_PositionU j = currentPos + 1; |
| 413 | char ch = styler.SafeGetCharAt(j); |
| 414 | while ((j < endPos) && (IsASpaceOrTab(ch) || ch == '\r' || ch == '\n' || |
| 415 | IsStreamCommentStyle(styler.StyleAt(j)) || (includeChars && setWord.Contains(ch)))) { |
| 416 | j++; |
| 417 | ch = styler.SafeGetCharAt(j); |
| 418 | } |
| 419 | return j; |
| 420 | } |
| 421 | |
| 422 | static void ClassifyPascalWordFoldPoint(int &levelCurrent, int &lineFoldStateCurrent, |
| 423 | Sci_Position startPos, Sci_PositionU endPos, |
| 424 | Sci_PositionU lastStart, Sci_PositionU currentPos, Accessor &styler) { |
| 425 | char s[100]; |
| 426 | GetRangeLowered(lastStart, currentPos, styler, s, sizeof(s)); |
| 427 | |
| 428 | if (strcmp(s, "record" ) == 0) { |
| 429 | lineFoldStateCurrent |= stateFoldInRecord; |
| 430 | levelCurrent++; |
| 431 | } else if (strcmp(s, "begin" ) == 0 || |
| 432 | strcmp(s, "asm" ) == 0 || |
| 433 | strcmp(s, "try" ) == 0 || |
| 434 | (strcmp(s, "case" ) == 0 && !(lineFoldStateCurrent & stateFoldInRecord))) { |
| 435 | levelCurrent++; |
| 436 | } else if (strcmp(s, "class" ) == 0 || strcmp(s, "object" ) == 0) { |
| 437 | // "class" & "object" keywords require special handling... |
| 438 | bool ignoreKeyword = false; |
| 439 | Sci_PositionU j = SkipWhiteSpace(currentPos, endPos, styler); |
| 440 | if (j < endPos) { |
| 441 | CharacterSet setWordStart(CharacterSet::setAlpha, "_" ); |
| 442 | CharacterSet setWord(CharacterSet::setAlphaNum, "_" ); |
| 443 | |
| 444 | if (styler.SafeGetCharAt(j) == ';') { |
| 445 | // Handle forward class declarations ("type TMyClass = class;") |
| 446 | // and object method declarations ("TNotifyEvent = procedure(Sender: TObject) of object;") |
| 447 | ignoreKeyword = true; |
| 448 | } else if (strcmp(s, "class" ) == 0) { |
| 449 | // "class" keyword has a few more special cases... |
| 450 | if (styler.SafeGetCharAt(j) == '(') { |
| 451 | // Handle simplified complete class declarations ("type TMyClass = class(TObject);") |
| 452 | j = SkipWhiteSpace(j, endPos, styler, true); |
| 453 | if (j < endPos && styler.SafeGetCharAt(j) == ')') { |
| 454 | j = SkipWhiteSpace(j, endPos, styler); |
| 455 | if (j < endPos && styler.SafeGetCharAt(j) == ';') { |
| 456 | ignoreKeyword = true; |
| 457 | } |
| 458 | } |
| 459 | } else if (setWordStart.Contains(styler.SafeGetCharAt(j))) { |
| 460 | char s2[11]; // Size of the longest possible keyword + one additional character + null |
| 461 | GetForwardRangeLowered(j, setWord, styler, s2, sizeof(s2)); |
| 462 | |
| 463 | if (strcmp(s2, "procedure" ) == 0 || |
| 464 | strcmp(s2, "function" ) == 0 || |
| 465 | strcmp(s2, "of" ) == 0 || |
| 466 | strcmp(s2, "var" ) == 0 || |
| 467 | strcmp(s2, "property" ) == 0 || |
| 468 | strcmp(s2, "operator" ) == 0) { |
| 469 | ignoreKeyword = true; |
| 470 | } |
| 471 | } |
| 472 | } |
| 473 | } |
| 474 | if (!ignoreKeyword) { |
| 475 | levelCurrent++; |
| 476 | } |
| 477 | } else if (strcmp(s, "interface" ) == 0) { |
| 478 | // "interface" keyword requires special handling... |
| 479 | bool ignoreKeyword = true; |
| 480 | Sci_Position j = lastStart - 1; |
| 481 | char ch = styler.SafeGetCharAt(j); |
| 482 | while ((j >= startPos) && (IsASpaceOrTab(ch) || ch == '\r' || ch == '\n' || |
| 483 | IsStreamCommentStyle(styler.StyleAt(j)))) { |
| 484 | j--; |
| 485 | ch = styler.SafeGetCharAt(j); |
| 486 | } |
| 487 | if (j >= startPos && styler.SafeGetCharAt(j) == '=') { |
| 488 | ignoreKeyword = false; |
| 489 | } |
| 490 | if (!ignoreKeyword) { |
| 491 | Sci_PositionU k = SkipWhiteSpace(currentPos, endPos, styler); |
| 492 | if (k < endPos && styler.SafeGetCharAt(k) == ';') { |
| 493 | // Handle forward interface declarations ("type IMyInterface = interface;") |
| 494 | ignoreKeyword = true; |
| 495 | } |
| 496 | } |
| 497 | if (!ignoreKeyword) { |
| 498 | levelCurrent++; |
| 499 | } |
| 500 | } else if (strcmp(s, "dispinterface" ) == 0) { |
| 501 | // "dispinterface" keyword requires special handling... |
| 502 | bool ignoreKeyword = false; |
| 503 | Sci_PositionU j = SkipWhiteSpace(currentPos, endPos, styler); |
| 504 | if (j < endPos && styler.SafeGetCharAt(j) == ';') { |
| 505 | // Handle forward dispinterface declarations ("type IMyInterface = dispinterface;") |
| 506 | ignoreKeyword = true; |
| 507 | } |
| 508 | if (!ignoreKeyword) { |
| 509 | levelCurrent++; |
| 510 | } |
| 511 | } else if (strcmp(s, "end" ) == 0) { |
| 512 | lineFoldStateCurrent &= ~stateFoldInRecord; |
| 513 | levelCurrent--; |
| 514 | if (levelCurrent < SC_FOLDLEVELBASE) { |
| 515 | levelCurrent = SC_FOLDLEVELBASE; |
| 516 | } |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | static void FoldPascalDoc(Sci_PositionU startPos, Sci_Position length, int initStyle, WordList *[], |
| 521 | Accessor &styler) { |
| 522 | bool = styler.GetPropertyInt("fold.comment" ) != 0; |
| 523 | bool foldPreprocessor = styler.GetPropertyInt("fold.preprocessor" ) != 0; |
| 524 | bool foldCompact = styler.GetPropertyInt("fold.compact" , 1) != 0; |
| 525 | Sci_PositionU endPos = startPos + length; |
| 526 | int visibleChars = 0; |
| 527 | Sci_Position lineCurrent = styler.GetLine(startPos); |
| 528 | int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK; |
| 529 | int levelCurrent = levelPrev; |
| 530 | int lineFoldStateCurrent = lineCurrent > 0 ? styler.GetLineState(lineCurrent - 1) & stateFoldMaskAll : 0; |
| 531 | char chNext = styler[startPos]; |
| 532 | int styleNext = styler.StyleAt(startPos); |
| 533 | int style = initStyle; |
| 534 | |
| 535 | Sci_Position lastStart = 0; |
| 536 | CharacterSet setWord(CharacterSet::setAlphaNum, "_" , 0x80, true); |
| 537 | |
| 538 | for (Sci_PositionU i = startPos; i < endPos; i++) { |
| 539 | char ch = chNext; |
| 540 | chNext = styler.SafeGetCharAt(i + 1); |
| 541 | int stylePrev = style; |
| 542 | style = styleNext; |
| 543 | styleNext = styler.StyleAt(i + 1); |
| 544 | bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n'); |
| 545 | |
| 546 | if (foldComment && IsStreamCommentStyle(style)) { |
| 547 | if (!IsStreamCommentStyle(stylePrev)) { |
| 548 | levelCurrent++; |
| 549 | } else if (!IsStreamCommentStyle(styleNext) && !atEOL) { |
| 550 | // Comments don't end at end of line and the next character may be unstyled. |
| 551 | levelCurrent--; |
| 552 | } |
| 553 | } |
| 554 | if (foldComment && atEOL && IsCommentLine(lineCurrent, styler)) |
| 555 | { |
| 556 | if (!IsCommentLine(lineCurrent - 1, styler) |
| 557 | && IsCommentLine(lineCurrent + 1, styler)) |
| 558 | levelCurrent++; |
| 559 | else if (IsCommentLine(lineCurrent - 1, styler) |
| 560 | && !IsCommentLine(lineCurrent+1, styler)) |
| 561 | levelCurrent--; |
| 562 | } |
| 563 | if (foldPreprocessor) { |
| 564 | if (style == SCE_PAS_PREPROCESSOR && ch == '{' && chNext == '$') { |
| 565 | ClassifyPascalPreprocessorFoldPoint(levelCurrent, lineFoldStateCurrent, i + 2, styler); |
| 566 | } else if (style == SCE_PAS_PREPROCESSOR2 && ch == '(' && chNext == '*' |
| 567 | && styler.SafeGetCharAt(i + 2) == '$') { |
| 568 | ClassifyPascalPreprocessorFoldPoint(levelCurrent, lineFoldStateCurrent, i + 3, styler); |
| 569 | } |
| 570 | } |
| 571 | |
| 572 | if (stylePrev != SCE_PAS_WORD && style == SCE_PAS_WORD) |
| 573 | { |
| 574 | // Store last word start point. |
| 575 | lastStart = i; |
| 576 | } |
| 577 | if (stylePrev == SCE_PAS_WORD && !(lineFoldStateCurrent & stateFoldInPreprocessor)) { |
| 578 | if(setWord.Contains(ch) && !setWord.Contains(chNext)) { |
| 579 | ClassifyPascalWordFoldPoint(levelCurrent, lineFoldStateCurrent, startPos, endPos, lastStart, i, styler); |
| 580 | } |
| 581 | } |
| 582 | |
| 583 | if (!IsASpace(ch)) |
| 584 | visibleChars++; |
| 585 | |
| 586 | if (atEOL) { |
| 587 | int lev = levelPrev; |
| 588 | if (visibleChars == 0 && foldCompact) |
| 589 | lev |= SC_FOLDLEVELWHITEFLAG; |
| 590 | if ((levelCurrent > levelPrev) && (visibleChars > 0)) |
| 591 | lev |= SC_FOLDLEVELHEADERFLAG; |
| 592 | if (lev != styler.LevelAt(lineCurrent)) { |
| 593 | styler.SetLevel(lineCurrent, lev); |
| 594 | } |
| 595 | int newLineState = (styler.GetLineState(lineCurrent) & ~stateFoldMaskAll) | lineFoldStateCurrent; |
| 596 | styler.SetLineState(lineCurrent, newLineState); |
| 597 | lineCurrent++; |
| 598 | levelPrev = levelCurrent; |
| 599 | visibleChars = 0; |
| 600 | } |
| 601 | } |
| 602 | |
| 603 | // If we didn't reach the EOL in previous loop, store line level and whitespace information. |
| 604 | // The rest will be filled in later... |
| 605 | int lev = levelPrev; |
| 606 | if (visibleChars == 0 && foldCompact) |
| 607 | lev |= SC_FOLDLEVELWHITEFLAG; |
| 608 | styler.SetLevel(lineCurrent, lev); |
| 609 | } |
| 610 | |
| 611 | static const char * const pascalWordListDesc[] = { |
| 612 | "Keywords" , |
| 613 | 0 |
| 614 | }; |
| 615 | |
| 616 | LexerModule lmPascal(SCLEX_PASCAL, ColourisePascalDoc, "pascal" , FoldPascalDoc, pascalWordListDesc); |
| 617 | |