From cee1374bbb18d04b9fb647cc02104b09430e7628 Mon Sep 17 00:00:00 2001 From: zufuliu Date: Sun, 5 Jan 2025 18:27:33 +0800 Subject: [PATCH] Rework VB code folding. --- scintilla/lexers/LexVB.cxx | 371 +++++++++++++++++++------------------ tools/lang/VB.NET.vb | 5 +- 2 files changed, 194 insertions(+), 182 deletions(-) diff --git a/scintilla/lexers/LexVB.cxx b/scintilla/lexers/LexVB.cxx index 48d55724ef..c0195c6c90 100644 --- a/scintilla/lexers/LexVB.cxx +++ b/scintilla/lexers/LexVB.cxx @@ -37,8 +37,11 @@ enum class Language { enum class KeywordType { None, End, - AccessModifier, Function, + Select, // Select Case + Property, // Property Get, Set, Let + SkipWhile, // Do While, Loop While, Skip While, Take While + CustomEvent, // Custom Event Preprocessor, }; @@ -46,9 +49,11 @@ enum { VBLineType_CommentLine = 1, VBLineType_DimLine = 2, VBLineType_ConstLine = 3, - VBLineType_VB6TypeLine = 4, + VBLineStateStringInterpolation = 4, VBLineStateLineContinuation = 1 << 3, - VBLineStateStringInterpolation = 1 << 4, + VBLineStateInterfaceBlock = 1 << 4, + VBLineStatePropertyBlock = 1 << 5, + VBLineStateMultilineMask = VBLineStateLineContinuation | VBLineStateInterfaceBlock | VBLineStatePropertyBlock, }; //KeywordIndex++Autogenerated -- start of section automatically generated @@ -67,8 +72,6 @@ enum { }; //KeywordIndex--Autogenerated -- end of section automatically generated -#define LexCharAt(pos) styler.SafeGetCharAt(pos) - // https://learn.microsoft.com/en-us/dotnet/visual-basic/reference/language-specification/lexical-grammar#type-characters // https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/data-type-summary constexpr bool IsTypeCharacter(int ch) noexcept { @@ -111,8 +114,11 @@ inline bool IsInterpolatedStringEnd(const StyleContext &sc) noexcept { void ColouriseVBDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, LexerWordList keywordLists, Accessor &styler) { KeywordType kwType = KeywordType::None; bool preprocessor = false; + bool declareDelegate = false; // Declare, Delegate {Function Sub} + bool openFor = false; // VBA: Open For Access int lineState = 0; int parenCount = 0; + int beginEnd = 0; // VB6 Form: nested Begin ... End int fileNbDigits = 0; int visibleChars = 0; int chBefore = 0; @@ -129,8 +135,9 @@ void ColouriseVBDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyl StyleContext sc(startPos, lengthDoc, initStyle, styler); if (sc.currentLine > 0) { lineState = styler.GetLineState(sc.currentLine - 1); + beginEnd = (lineState >> 8) & 0xff; parenCount = lineState >> 16; - lineState &= VBLineStateLineContinuation; + lineState &= VBLineStateMultilineMask; } if (startPos != 0 && IsSpaceEquiv(initStyle)) { LookbackNonWhite(styler, startPos, SCE_VB_LINE_CONTINUATION, chPrevNonWhite, stylePrevNonWhite); @@ -197,27 +204,58 @@ void ColouriseVBDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyl sc.ChangeState(SCE_VB_PREPROCESSOR_WORD); } } else if (StrEqual(s, "dim")) { - lineState = VBLineType_DimLine; + lineState |= VBLineType_DimLine; } else if (StrEqual(s, "const")) { - lineState = VBLineType_ConstLine; - } else if (StrEqual(s, "type")) { - if (visibleChars == len || kwPrev == KeywordType::AccessModifier) { - lineState = VBLineType_VB6TypeLine; - } + lineState |= VBLineType_ConstLine; + } else if (StrEqual(s, "exit")) { + kwType = KeywordType::End; } else if (StrEqual(s, "end")) { kwType = KeywordType::End; + if (beginEnd > 0) { + --beginEnd; + } else if (!IsAlpha(chNext)) { + sc.ChangeState(SCE_VB_KEYWORD3); + } } else if (StrEqualsAny(s, "sub", "function")) { - if (kwPrev != KeywordType::End) { + if (kwPrev != KeywordType::End && chNext != '(') { kwType = KeywordType::Function; } - } else if (StrEqualsAny(s, "public", "private")) { - kwType = KeywordType::AccessModifier; + if (declareDelegate || (lineState & VBLineStateInterfaceBlock) != 0) { + declareDelegate = false; + sc.ChangeState(SCE_VB_KEYWORD3); + } + } else if (language != Language::VBScript) { + if (StrEqual(s, "declare") || (language == Language::VBNET && StrEqual(s, "delegate"))) { + declareDelegate = true; + } else if (StrEqual(s, "class")) { + if (language == Language::VBA || (lineState & VBLineStateInterfaceBlock) != 0) { + sc.ChangeState(SCE_VB_KEYWORD3); + } + } else if (language == Language::VBNET) { + if (StrEqualsAny(s, "interface", "property")) { + if (kwPrev == KeywordType::End) { + lineState = 0; + } else if ((lineState & VBLineStateInterfaceBlock) != 0) { + sc.ChangeState(SCE_VB_KEYWORD3); + } else { + lineState |= (s[0] == 'i') ? VBLineStateInterfaceBlock : VBLineStatePropertyBlock; + } + } + } else if (openFor && StrEqual(s, "for")) { + openFor = false; + sc.ChangeState(SCE_VB_KEYWORD3); + } } } } else if (keywordLists[KeywordIndex_VBAKeyword].InList(s)) { sc.ChangeState(SCE_VB_KEYWORD3); if (language == Language::VBA && !skipType && chBefore != '.') { sc.ChangeState(SCE_VB_KEYWORD); + if (StrEqual(s, "open")) { + openFor = true; + } else if (visibleChars == 5 && StrEqual(s, "begin")) { + ++beginEnd; + } } } else if (keywordLists[KeywordIndex_TypeKeyword].InList(s)) { sc.ChangeState(SCE_VB_KEYWORD2); @@ -299,8 +337,9 @@ void ColouriseVBDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyl case SCE_VB_COMMENTLINE: if (sc.atLineStart) { - if (lineState == VBLineStateLineContinuation) { - lineState = VBLineType_CommentLine; + if (lineState & VBLineStateLineContinuation) { + lineState &= ~VBLineStateLineContinuation; + lineState |= VBLineType_CommentLine; } else { sc.SetState(SCE_VB_DEFAULT); } @@ -352,7 +391,7 @@ void ColouriseVBDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyl if (sc.ch == '\'') { sc.SetState(SCE_VB_COMMENTLINE); if (visibleChars == 0) { - lineState = VBLineType_CommentLine; + lineState |= VBLineType_CommentLine; } } else if (sc.ch == '\"') { sc.SetState(SCE_VB_STRING); @@ -410,11 +449,13 @@ void ColouriseVBDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyl if (!nestedState.empty()) { lineState |= VBLineStateStringInterpolation; } - styler.SetLineState(sc.currentLine, lineState | (parenCount << 16)); - lineState &= VBLineStateLineContinuation; + styler.SetLineState(sc.currentLine, lineState | (beginEnd << 8) | (parenCount << 16)); + lineState &= VBLineStateMultilineMask; visibleChars = 0; kwType = KeywordType::None; preprocessor = false; + declareDelegate = false; + openFor = false; } sc.Forward(); } @@ -422,46 +463,22 @@ void ColouriseVBDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyl sc.Complete(); } -bool VBMatchNextWord(LexAccessor &styler, Sci_Position startPos, Sci_Position endPos, const char *word) noexcept { - const Sci_Position pos = LexSkipSpaceTab(styler, startPos, endPos); - return isspacechar(LexCharAt(pos + static_cast(strlen(word)))) - && styler.MatchLowerCase(pos, word); -} -int IsVBProperty(LexAccessor &styler, Sci_Line line, Sci_Position startPos) noexcept { - const Sci_Position endPos = styler.LineStart(line + 1) - 1; - bool visibleChars = false; - for (Sci_Position i = startPos; i < endPos; i++) { - const uint8_t ch = UnsafeLower(styler[i]); - const int style = styler.StyleAt(i); - if (style == SCE_VB_OPERATOR && ch == '(') { - return true; - } - if (style == SCE_VB_KEYWORD && !visibleChars - && (ch == 'g' || ch == 'l' || ch == 's') - && UnsafeLower(styler[i + 1]) == 'e' - && UnsafeLower(styler[i + 2]) == 't' - && isspacechar(styler[i + 3])) { - return 2; - } - if (ch > ' ') { - visibleChars = true; - } - } - return false; -} - -#define VBMatch(word) styler.MatchLowerCase(i, word) -#define VBMatchNext(pos, word) VBMatchNextWord(styler, pos, endPos, word) - struct FoldLineState { int lineState; constexpr explicit FoldLineState(int lineState_) noexcept : lineState(lineState_) {} int GetLineType() const noexcept { return lineState & 3; } + int IsInterfaceBlock() const noexcept { + return (lineState & VBLineStateInterfaceBlock); + } + int IsPropertyBlock() const noexcept { + return (lineState & VBLineStatePropertyBlock); + } }; void FoldVBDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, LexerWordList /*keywordLists*/, Accessor &styler) { + const Language language = static_cast(styler.GetPropertyInt("lexer.lang")); const Sci_PositionU endPos = startPos + lengthDoc; Sci_Line lineCurrent = styler.GetLine(startPos); FoldLineState foldPrev(0); @@ -472,149 +489,142 @@ void FoldVBDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, Le } int levelNext = levelCurrent; + initStyle = styler.StyleIndexAt(startPos); FoldLineState foldCurrent(styler.GetLineState(lineCurrent)); Sci_PositionU lineStartNext = styler.LineStart(lineCurrent + 1); - int style = initStyle; - int styleNext = styler.StyleAt(startPos); - - int visibleChars = 0; - int numBegin = 0; // nested Begin ... End, found in VB6 Form - bool isEnd = false; // End {Function Sub}{If}{Class Module Structure Interface Operator Enum}{Property Event}{Type} - bool isInterface = false;// {Property Function Sub Event Interface Class Structure } - bool isProperty = false;// Property: Get Set - bool isCustom = false; // Custom Event - bool isExit = false; // Exit {Function Sub Property} - bool isDeclare = false; // Declare, Delegate {Function Sub} - int ifThenMask = 0; // If ... Then \r\n ... \r\n End If + KeywordType kwType = KeywordType::None; + bool lambdaExpr = false; + int ifThenMask = 0; // If ... Then ... End If + int wordLen = 0; + char s[MaxKeywordSize]; + memset(s, 4, 4); while (startPos < endPos) { - const Sci_PositionU i = startPos; - const int stylePrev = style; - style = styleNext; + const int style = initStyle; const char ch = styler[startPos]; - styleNext = styler.StyleAt(++startPos); - - if (style == SCE_VB_KEYWORD && stylePrev != SCE_VB_KEYWORD) { // not a member, not bracketed [keyword] identifier - if (visibleChars == 0 && (VBMatch("for") || (VBMatch("do") && isspacechar(LexCharAt(i + 2))) // not Double - || VBMatch("while") || (VBMatch("try") && isspacechar(LexCharAt(i + 3))) // not TryCast - || (VBMatch("select") && VBMatchNext(i + 6, "case")) // Select Case - || (VBMatch("with") && isspacechar(LexCharAt(i + 4))) // not WithEvents, not With {...} - || VBMatch("namespace") || VBMatch("synclock") || VBMatch("using") - || (isProperty && (VBMatch("set") || (VBMatch("get") && isspacechar(LexCharAt(i + 3))))) // not GetType - || (isCustom && (VBMatch("raiseevent") || VBMatch("addhandler") || VBMatch("removehandler"))) - )) { - levelNext++; - } else if (visibleChars == 0 && (VBMatch("next") || VBMatch("loop") || VBMatch("wend"))) { - levelNext--; - } else if (VBMatch("exit") && (VBMatchNext(i + 4, "function") || VBMatchNext(i + 4, "sub") - || VBMatchNext(i + 4, "property") - )) { - isExit = true; - } else if (VBMatch("begin")) { - levelNext++; - if (isspacechar(LexCharAt(i + 5))) - numBegin++; - } else if (VBMatch("end")) { - levelNext--; - int chEnd = static_cast(LexCharAt(i + 3)); - if (chEnd == ' ' || chEnd == '\t') { - const Sci_Position pos = LexSkipSpaceTab(styler, i + 3, endPos); - chEnd = static_cast(LexCharAt(pos)); - // check if End is used to terminate statement - if (IsAlpha(chEnd) && (VBMatchNext(pos, "function") || VBMatchNext(pos, "sub") - || VBMatchNext(pos, "if") || VBMatchNext(pos, "class") || VBMatchNext(pos, "structure") - || VBMatchNext(pos, "module") || VBMatchNext(pos, "enum") || VBMatchNext(pos, "interface") - || VBMatchNext(pos, "operator") || VBMatchNext(pos, "property") || VBMatchNext(pos, "event") - || VBMatchNext(pos, "type") // VB6 - )) { - isEnd = true; + initStyle = styler.StyleIndexAt(++startPos); + + if (style == SCE_VB_KEYWORD || style == SCE_VB_PREPROCESSOR) { + if (wordLen < MaxKeywordSize - 1) { + s[wordLen++] = UnsafeLower(ch); + } + if (style != initStyle) { + s[wordLen] = '\0'; + wordLen = 0; + if (style == SCE_VB_KEYWORD) { + const KeywordType kwPrev = kwType; + kwType = KeywordType::None; + if (StrEqual(s, "end") || (language == Language::VBA && StrStartsWith(s, "end"))) { + kwType = KeywordType::End; + levelNext--; + // one line: If ... Then ... End If + if (ifThenMask == 3) { + levelNext++; + } + ifThenMask = 0; + } else if (StrEqualsAny(s, "exit", "resume")) { + kwType = KeywordType::End; + } else if (kwPrev != KeywordType::End) { + const int chNext = LexGetNextChar(styler, startPos, lineStartNext); + if (StrEqual(s, "if")) { + ifThenMask = 1; + levelNext++; + } else if (StrEqualsAny(s, "for", "class")) { + levelNext++; + } else if (StrEqualsAny(s, "next", "wend")) { + levelNext--; + } else if (StrEqual(s, "do")) { + kwType = KeywordType::SkipWhile; + levelNext++; + } else if (StrEqual(s, "loop")) { + kwType = KeywordType::SkipWhile; + levelNext--; + } else if (StrEqual(s, "case")) { + if (kwPrev == KeywordType::Select) { + levelNext++; + } + } else if (StrEqual(s, "while")) { + if (kwPrev != KeywordType::SkipWhile) { + levelNext++; + } + } else if (StrEqualsAny(s, "get", "set", "let")) { + if (kwPrev == KeywordType::Property || (foldCurrent.IsPropertyBlock() && !StrEqual(s, "let"))) { + levelNext++; + } + } else if (StrEqual(s, "select")) { + kwType = KeywordType::Select; + } else if (StrEqual(s, "property")) { + kwType = KeywordType::Property; + if (language == Language::VBNET) { + levelNext++; + } + } else if (StrEqualsAny(s, "sub", "function")) { + levelNext++; + if (chNext == '(' && language == Language::VBNET) { + lambdaExpr = true; + } + } else if (StrEqual(s, "with")) { + if (chNext != '{') { + levelNext++; + } + } else if (StrEqual(s, "then")) { + if (ifThenMask & 1) { + ifThenMask |= 2; + // check single line If ... Then ... + if (chNext != '\0' && chNext != '\'') { + levelNext--; + } + } + } else if (language != Language::VBScript) { + if (StrEqual(s, "enum")) { + levelNext++; + } else if (language == Language::VBNET) { + if (StrEqual(s, "continue")) { + kwType = KeywordType::End; // Continue Do, For, While + } else if (StrEqualsAny(s, "skip", "take")) { + kwType = KeywordType::SkipWhile; + } else if (StrEqual(s, "custom")) { + kwType = KeywordType::CustomEvent; + } else if (StrEqualsAny(s, "try", "using", "module", "operator", "synclock", "interface", "namespace") + || (kwPrev == KeywordType::CustomEvent && StrEqual(s, "event")) + || (chNext == '(' && StrEqualsAny(s, "addhandler", "removehandler", "raiseevent")) + || (!foldCurrent.IsInterfaceBlock() && StrEqual(s, "structure"))) { + levelNext++; + } + } else { + if (StrEqual(s, "type") || StrStartsWith(s, "begin")) { + levelNext++; + } + } + } } - } - if (chEnd == '\r' || chEnd == '\n' || chEnd == '\'') { - isEnd = false; - if (numBegin == 0) levelNext++;// End can be placed anywhere, but not used to terminate statement - if (numBegin > 0) numBegin--; - } - // one line: If ... Then ... End If - if (ifThenMask == 3) { - levelNext++; - } - ifThenMask = 0; - } else if (VBMatch("if")) { - if (isEnd) { - isEnd = false; } else { - ifThenMask = 1; - levelNext++; - } - } else if (VBMatch("then")) { - if (ifThenMask & 1) { - ifThenMask |= 2; - const Sci_Position pos = LexSkipSpaceTab(styler, i + 4, endPos); - const char chEnd = LexCharAt(pos); - if (!(chEnd == '\r' || chEnd == '\n' || chEnd == '\'')) + kwType = KeywordType::None; + if (StrStartsWith(s, "#end")) { levelNext--; + } else if (StrEqualsAny(s, "#if", "#region", "#externalsource")) { + levelNext++; + } } - } else if ((!isInterface && (VBMatch("class") || VBMatch("structure"))) - || VBMatch("module") || VBMatch("enum") || VBMatch("operator") - ) { - if (isEnd) isEnd = false; - else levelNext++; - } else if (VBMatch("interface")) { - if (!(isEnd || isInterface)) - levelNext++; - isInterface = true; - if (isEnd) { - isEnd = false; isInterface = false; - } - } else if (VBMatch("declare") || VBMatch("delegate")) { - isDeclare = true; - } else if (!isInterface && (VBMatch("sub") || VBMatch("function"))) { - if (!(isEnd || isExit || isDeclare)) - levelNext++; - if (isEnd) isEnd = false; - if (isExit) isExit = false; - if (isDeclare) isDeclare = false; - } else if (!isInterface && VBMatch("property")) { - isProperty = true; - if (!(isEnd || isExit)) { - const int result = IsVBProperty(styler, lineCurrent, i + 8); - levelNext += result != 0; - isProperty = result & true; - } - if (isEnd) { - isEnd = false; isProperty = false; - } - if (isExit) isExit = false; - } else if (VBMatch("custom")) { - isCustom = true; - } else if (!isInterface && isCustom && VBMatch("event")) { - if (isEnd) { - isEnd = false; isCustom = false; - } else levelNext++; - } else if (VBMatch("type") && isspacechar(LexCharAt(i + 4))) { // not TypeOf, VB6: [...] Type ... End Type - if (!isEnd && (foldCurrent.lineState & VBLineType_VB6TypeLine) != 0) - levelNext++; - if (isEnd) isEnd = false; } - } - else if (style == SCE_VB_PREPROCESSOR && stylePrev != SCE_VB_PREPROCESSOR) { - if (VBMatch("#if") || VBMatch("#region") || VBMatch("#externalsource")) - levelNext++; - else if (VBMatch("#end")) - levelNext--; - } - else if (style == SCE_VB_OPERATOR) { - // Anonymous With { ... } - if (AnyOf<'{', '}'>(ch)) { + } else if (style == SCE_VB_OPERATOR) { + kwType = KeywordType::None; + if (ch == ')' && lambdaExpr) { + lambdaExpr = false; + const int chNext = LexGetNextChar(styler, startPos, lineStartNext); + // single line lambda + if (chNext != '\0' && chNext != '_' && chNext != '\'') { + levelNext--; + } + } else if (AnyOf<'{', '}'>(ch)) { + // Anonymous With { ... } levelNext += ('{' + '}')/2 - ch; } + } else if (!IsSpaceEquiv(style)) { + kwType = KeywordType::None; } - if (visibleChars == 0 && !isspacechar(ch)) { - visibleChars++; - } if (startPos == lineStartNext) { const FoldLineState foldNext(styler.GetLineState(lineCurrent + 1)); levelNext = sci::max(levelNext, SC_FOLDLEVELBASE); @@ -639,8 +649,9 @@ void FoldVBDoc(Sci_PositionU startPos, Sci_Position lengthDoc, int initStyle, Le levelCurrent = levelNext; foldPrev = foldCurrent; foldCurrent = foldNext; - visibleChars = 0; + kwType = KeywordType::None; ifThenMask = 0; + lambdaExpr = false; } } } diff --git a/tools/lang/VB.NET.vb b/tools/lang/VB.NET.vb index f3a504b065..80f9ada51a 100644 --- a/tools/lang/VB.NET.vb +++ b/tools/lang/VB.NET.vb @@ -69,7 +69,7 @@ Imports Inherits Interface Of Inherits - Property + Property As Implements Function Sub Event @@ -98,7 +98,6 @@ Property As Implements Set() End Set End Property -Property As Implements RaiseEvent ReDim Preserve Rem @@ -163,6 +162,8 @@ Xor ' https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/procedures/lambda-expressions Function() And Sub() And +Async Sub() +End Sub ' https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/modifiers/ Ansi Assembly Async Auto