From 9211fe1b3d6a8e860ac24713bb109899a5dccea9 Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Thu, 3 Apr 2025 19:45:55 +0800 Subject: [PATCH 01/10] Moved helper function to util --- client/src/test/diagnostics.test.ts | 7 +------ client/src/test/formatting.test.ts | 7 +------ client/src/test/util.ts | 7 +++++++ 3 files changed, 9 insertions(+), 12 deletions(-) create mode 100644 client/src/test/util.ts diff --git a/client/src/test/diagnostics.test.ts b/client/src/test/diagnostics.test.ts index 6a54e96..cf860d0 100644 --- a/client/src/test/diagnostics.test.ts +++ b/client/src/test/diagnostics.test.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; import * as assert from 'assert'; import { getDocUri, activate } from './helper'; +import { toRange } from './util'; suite('Should get diagnostics', () => { test('diagnostics.class.missingNameAttributeError', async () => { @@ -143,12 +144,6 @@ suite('Should get diagnostics', () => { }); }); -function toRange(sLine: number, sChar: number, eLine: number, eChar: number) { - const start = new vscode.Position(sLine - 1, sChar); - const end = new vscode.Position(eLine - 1, eChar); - return new vscode.Range(start, end); -} - async function testDiagnostics(docUri: vscode.Uri, expectedDiagnostics: vscode.Diagnostic[]) { await activate(docUri); diff --git a/client/src/test/formatting.test.ts b/client/src/test/formatting.test.ts index 22a0383..828beec 100644 --- a/client/src/test/formatting.test.ts +++ b/client/src/test/formatting.test.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import * as assert from 'assert'; import { getDocUri, activate } from './helper'; +import { toRange } from './util'; suite('Should get text edits', () => { test('formatting.class.template', async () => { @@ -39,12 +40,6 @@ suite('Should get text edits', () => { }); }); -function toRange(sLine: number, sChar: number, eLine: number, eChar: number) { - const start = new vscode.Position(sLine - 1, sChar); - const end = new vscode.Position(eLine - 1, eChar); - return new vscode.Range(start, end); -} - async function testTextEdits(docUri: vscode.Uri, expectedTextEdits: vscode.TextEdit[]) { await activate(docUri); const actualEdits = await vscode.commands.executeCommand( diff --git a/client/src/test/util.ts b/client/src/test/util.ts new file mode 100644 index 0000000..be8dea1 --- /dev/null +++ b/client/src/test/util.ts @@ -0,0 +1,7 @@ +import { Position, Range } from 'vscode'; + +export function toRange(sLine: number, sChar: number, eLine: number, eChar: number) { + const start = new Position(sLine - 1, sChar); + const end = new Position(eLine - 1, eChar); + return new Range(start, end); +} \ No newline at end of file From d4a58340acab25fb6723ea045301fd8738e6945e Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Thu, 3 Apr 2025 19:59:26 +0800 Subject: [PATCH 02/10] Removed folding range from module --- server/src/project/elements/module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/project/elements/module.ts b/server/src/project/elements/module.ts index 94f2add..095ba27 100644 --- a/server/src/project/elements/module.ts +++ b/server/src/project/elements/module.ts @@ -32,13 +32,13 @@ abstract class BaseModuleElement extends BaseIdenti abstract hasOptionExplicit: boolean; settings: DocumentSettings; - foldingRangeCapability: FoldingRangeCapability; + // foldingRangeCapability: FoldingRangeCapability; symbolInformationCapability: SymbolInformationCapability; constructor(ctx: T, doc: TextDocument, documentSettings: DocumentSettings, symbolKind: SymbolKind) { super(ctx, doc); this.settings = documentSettings; - this.foldingRangeCapability = new FoldingRangeCapability(this); + // this.foldingRangeCapability = new FoldingRangeCapability(this); this.symbolInformationCapability = new SymbolInformationCapability(this, symbolKind); } From 4e06b479bd3473589b58628b54ba8f92e96ba3b7 Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Thu, 3 Apr 2025 20:04:01 +0800 Subject: [PATCH 03/10] Migrated legacy registers to new generic one --- server/src/project/parser/vbaListener.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/project/parser/vbaListener.ts b/server/src/project/parser/vbaListener.ts index 377a76f..48b3b36 100644 --- a/server/src/project/parser/vbaListener.ts +++ b/server/src/project/parser/vbaListener.ts @@ -107,7 +107,7 @@ export class VbaListener extends vbaListener { enterAnyOperator = (ctx: AnyOperatorContext) => { const element = new DuplicateOperatorElement(ctx, this.document.textDocument); - this.document.registerDiagnosticElement(element); + this.document.registerElement(element); } enterEnumDeclaration = (ctx: EnumDeclarationContext) => { @@ -131,7 +131,7 @@ export class VbaListener extends vbaListener { enterIgnoredClassAttr = (ctx: IgnoredClassAttrContext) => this.registerIgnoredAttribute(ctx); enterIgnoredProceduralAttr = (ctx: IgnoredProceduralAttrContext) => this.registerIgnoredAttribute(ctx); private registerIgnoredAttribute(ctx: IgnoredClassAttrContext | IgnoredProceduralAttrContext) { - this.document.registerDiagnosticElement(new ModuleIgnoredAttributeElement(ctx, this.document.textDocument)) + this.document.registerElement(new ModuleIgnoredAttributeElement(ctx, this.document.textDocument)) } enterProceduralModule = (ctx: ProceduralModuleContext) => { @@ -205,7 +205,7 @@ export class VbaListener extends vbaListener { enterWhileStatement = (ctx: WhileStatementContext) => { const element = new WhileLoopElement(ctx, this.document.textDocument) - this.document.registerDiagnosticElement(element); + this.document.registerElement(element); }; visitErrorNode(node: ErrorNode) { From 26cbb0a125b878e94da14c2ceb349afdfe6bce69 Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Thu, 3 Apr 2025 20:04:25 +0800 Subject: [PATCH 04/10] Add logging to onFoldingRange --- server/src/project/workspace.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/project/workspace.ts b/server/src/project/workspace.ts index a6660f5..f6e3fa2 100644 --- a/server/src/project/workspace.ts +++ b/server/src/project/workspace.ts @@ -333,6 +333,7 @@ class WorkspaceEvents { } private async onFoldingRangesAsync(params: FoldingRangeParams, token: CancellationToken): Promise { + Services.logger.debug('[Event] onFoldingRanges') let document: BaseProjectDocument | undefined; try { document = await this.getParsedProjectDocument(params.textDocument.uri, 0, token); @@ -341,9 +342,11 @@ class WorkspaceEvents { if (!!(error instanceof ParseCancellationException)) { throw error; } - // this.workspace.connection.window.showInformationMessage(`Parser error: ${error}`); } const result = document?.languageServerFoldingRanges(); + for (const foldingRange of result ?? []) { + Services.logger.debug(`${JSON.stringify(foldingRange)}`, 1); + } return result ?? []; } From d65a53c35b8b5251a007d862de55e005e04f212b Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Fri, 4 Apr 2025 11:58:59 +0800 Subject: [PATCH 05/10] Moved ParserContext line endings methods to extensions --- server/src/capabilities/capabilities.ts | 7 ++ server/src/extensions/parserExtensions.ts | 88 +++++++++++++++++++++++ server/src/project/parser/vbaListener.ts | 42 ++--------- 3 files changed, 102 insertions(+), 35 deletions(-) diff --git a/server/src/capabilities/capabilities.ts b/server/src/capabilities/capabilities.ts index 264eecf..ebbeb8c 100644 --- a/server/src/capabilities/capabilities.ts +++ b/server/src/capabilities/capabilities.ts @@ -30,6 +30,13 @@ export class FoldingRangeCapability extends BaseCapability { foldingRangeKind?: FoldingRangeKind; get foldingRange(): FoldingRange { + const trailingLineCount = this.element.context.rule.countTrailingLineEndings(); + const start = this.element.context.range.start; + const end = { + line: this.element.context.range.end.line - trailingLineCount, + character: this.element.context.range.end.character + } + const range = Range.create(start, end); return new FoldingRange(this.element.context.range, this.foldingRangeKind); } diff --git a/server/src/extensions/parserExtensions.ts b/server/src/extensions/parserExtensions.ts index ab09eb5..041dc6e 100644 --- a/server/src/extensions/parserExtensions.ts +++ b/server/src/extensions/parserExtensions.ts @@ -12,9 +12,12 @@ import { BuiltinTypeContext, ClassTypeNameContext, ConstItemContext, + EndOfStatementContext, + EndOfStatementNoWsContext, GlobalVariableDeclarationContext, PrivateConstDeclarationContext, PrivateVariableDeclarationContext, + ProcedureTailContext, PublicConstDeclarationContext, PublicVariableDeclarationContext, TypeSpecContext, @@ -22,6 +25,7 @@ import { VariableDclContext, WitheventsVariableDclContext } from '../antlr/out/vbaParser'; +import { LineEndingContext } from '../antlr/out/vbafmtParser'; declare module 'antlr4ng' { @@ -31,6 +35,8 @@ declare module 'antlr4ng' { startIndex(): number; stopIndex(): number; hasPositionOf(ctx: ParserRuleContext): boolean; + endsWithLineEnding: boolean; + countTrailingLineEndings(): number; } interface TerminalNode { @@ -125,6 +131,88 @@ ParserRuleContext.prototype.hasPositionOf = function (ctx: ParserRuleContext): b return this.startIndex() === ctx.startIndex() && this.stopIndex() === ctx.stopIndex(); } +Object.defineProperty(ParserRuleContext.prototype, 'endsWithLineEnding', { + get: function endsWithLineEnding() { + // Ensure we have a context. + if (!(this instanceof ParserRuleContext)) + return false; + + // Check last child is a line ending. + const child = this.children.at(-1); + if (!child) + return false; + + // Check the various line ending contexts. + if (child instanceof LineEndingContext) + return true; + if (child instanceof EndOfStatementContext) + return true; + if (child instanceof EndOfStatementNoWsContext) + return true; + if (child instanceof ProcedureTailContext) + return true; + + // Run it again! + if (child.getChildCount() > 0) + return (child as ParserRuleContext).endsWithLineEnding; + + // Not a line ending and no more children. + return false; + } +}) + +interface LineEndingParserRuleContext { + NEWLINE(): TerminalNode | null; +} + +function isLineEndingParserRuleContext(ctx: unknown): ctx is LineEndingParserRuleContext { + return typeof ctx === 'object' + && ctx !== null + && typeof (ctx as any).NEWLINE === 'function'; +} + +function countTrailingLineEndings(ctx: ParserRuleContext): number { + // This function recursively loops through last child of + // the context to find one that has a NEWLINE terminal node. + + // Check if we have a NEWLINE node. + if (isLineEndingParserRuleContext(ctx)) { + const lines = ctx.NEWLINE()?.getText(); + if (!lines) { + return 0; + } + + let i = 0; + let result = 0; + while (i < lines.length) { + const char = lines[i]; + + if (char === '\r') { + result++; + i += lines[i + 1] === '\n' ? 2 : 1; + } else if (char === '\n') { + result++; + i++; + } + } + + return result; + } + + // Recursive call on last child. + const lastChild = ctx.children.at(-1); + if (!!(lastChild instanceof ParserRuleContext)) { + return countTrailingLineEndings(lastChild); + } + + // If we get here, we have no trailing lines. + return 0; +} + +ParserRuleContext.prototype.countTrailingLineEndings = function (): number { + return countTrailingLineEndings(this); +} + TerminalNode.prototype.toRange = function (doc: TextDocument): Range { return Range.create( diff --git a/server/src/project/parser/vbaListener.ts b/server/src/project/parser/vbaListener.ts index 48b3b36..33eb41f 100644 --- a/server/src/project/parser/vbaListener.ts +++ b/server/src/project/parser/vbaListener.ts @@ -324,7 +324,7 @@ export class VbaFmtListener extends vbafmtListener { // Attributes are always zero indented. enterAttributeStatement = (ctx: AttributeStatementContext) => { const range = this.getCtxRange(ctx); - const offset = this.endsWithLineEnding(ctx) ? 0 : 1 + const offset = ctx.endsWithLineEnding ? 0 : 1 // Set the line after the end to what is current and then set current to zero. this.setIndentAt({ @@ -376,7 +376,7 @@ export class VbaFmtListener extends vbafmtListener { // Remove from continued and outdent next line. this.continuedElements.pop(); const doc = this.common.document.textDocument; - const offset = this.endsWithLineEnding(node) ? 0 : 1 + const offset = node.endsWithLineEnding ? 0 : 1 const line = node.toRange(doc).end.line + offset; this.modifyIndentAt({ line: line, @@ -417,7 +417,7 @@ export class VbaFmtListener extends vbafmtListener { this.activeElements.push(ctx); exitIndentAfterElement = (ctx: IndentAfterElementContext) => { - const offset = this.endsWithLineEnding(ctx) ? 0 : 1 + const offset = ctx.endsWithLineEnding ? 0 : 1 const line = this.getCtxRange(ctx).end.line + offset; this.modifyIndentAt({ line: line, @@ -460,7 +460,7 @@ export class VbaFmtListener extends vbafmtListener { // Exit outdent on enter / indent after exit element. exitOutdentOnIndentAfterElement = (ctx: OutdentOnIndentAfterElementContext) => { // Offset the line to indent based on whether the element ends with a new line character. - const offset = this.endsWithLineEnding(ctx) ? 0 : 1 + const offset = ctx.endsWithLineEnding ? 0 : 1 const line = this.getCtxRange(ctx).end.line + offset; this.modifyIndentAt({ line: line, @@ -522,7 +522,7 @@ export class VbaFmtListener extends vbafmtListener { // Get the previous case statement and outdent if it had a line ending. const caseElement = selectCaseElement.statements.at(-1); - if (!!caseElement && this.endsWithLineEnding(caseElement)) { + if (!!caseElement && caseElement.endsWithLineEnding) { this.outdentAfterExit({context: ctx}); } } @@ -540,7 +540,7 @@ export class VbaFmtListener extends vbafmtListener { // Get the previous case statement and outdent if it had a line ending. const caseElement = selectCaseElement.statements.at(-1); - if (!!caseElement && this.endsWithLineEnding(caseElement)) { + if (!!caseElement && caseElement.endsWithLineEnding) { this.modifyIndentAt({ line: this.getCtxRange(ctx).start.line, offset: -2, @@ -562,7 +562,7 @@ export class VbaFmtListener extends vbafmtListener { // Only indent if the case statement ends in a new line. // A new line indicates the case is a block type, not single line. - if (this.endsWithLineEnding(ctx)) { + if (ctx.endsWithLineEnding) { this.modifyIndentAt({ line: this.getCtxRange(ctx).end.line, offset: 2, @@ -713,32 +713,4 @@ export class VbaFmtListener extends vbafmtListener { private getCtxRange(ctx: ParserRuleContext): Range { return ctx.toRange(this.common.document.textDocument); } - - /** - * Checks if the context spills over into the next line. - * This is useful to prevent indentation of the wrong line. - * @param ctx A ParserRuleContext hopefully. - * @returns True if the last child is a LineEndingContext. - */ - private endsWithLineEnding(ctx: ParserRuleContext): boolean { - // Ensure we have a context. - if (!(ctx instanceof ParserRuleContext)) - return false; - - // Check last child is a line ending. - const child = ctx.children.at(-1); - if (!child) - return false; - - // Line endings don't have structures so no need to check children. - if (child instanceof LineEndingContext) - return true; - - // Run it again! - if (child.getChildCount() > 0) - return this.endsWithLineEnding(child as ParserRuleContext); - - // Not a line ending and no more children. - return false; - } } \ No newline at end of file From 5cc7ac4726501aa52840391c0d98e29464e2b7bf Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:01:12 +0800 Subject: [PATCH 06/10] Folding ranges now support text for logging --- server/src/capabilities/capabilities.ts | 4 ++- server/src/capabilities/folding.ts | 45 +++++++++++++++---------- server/src/project/workspace.ts | 4 +-- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/server/src/capabilities/capabilities.ts b/server/src/capabilities/capabilities.ts index ebbeb8c..5081ab3 100644 --- a/server/src/capabilities/capabilities.ts +++ b/server/src/capabilities/capabilities.ts @@ -28,6 +28,8 @@ abstract class BaseCapability { export class FoldingRangeCapability extends BaseCapability { foldingRangeKind?: FoldingRangeKind; + openWord?: string; + closeWord?: string; get foldingRange(): FoldingRange { const trailingLineCount = this.element.context.rule.countTrailingLineEndings(); @@ -37,7 +39,7 @@ export class FoldingRangeCapability extends BaseCapability { character: this.element.context.range.end.character } const range = Range.create(start, end); - return new FoldingRange(this.element.context.range, this.foldingRangeKind); + return new FoldingRange(range, this.foldingRangeKind, this.openWord, this.closeWord); } constructor(element: BaseContextSyntaxElement, foldingRangeKind?: FoldingRangeKind) { diff --git a/server/src/capabilities/folding.ts b/server/src/capabilities/folding.ts index 97c772b..5cb5a9c 100644 --- a/server/src/capabilities/folding.ts +++ b/server/src/capabilities/folding.ts @@ -24,33 +24,44 @@ export class FoldingRange implements VscFoldingRange { /** * The zero-based line number from where the folded range starts. */ - startLine: number; - - /** - * The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line. - */ - startCharacter?: number; + get startLine(): number { + return this._range.start.line; + } /** * The zero-based line number where the folded range ends. */ - endLine: number; - - /** - * The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line. - */ - endCharacter?: number; + get endLine(): number { + return this._range.end.line; + } /** * Describes the kind of the folding range such as 'comment' or 'region'. The kind * is used to categorize folding ranges and used by commands like 'Fold all comments'. See * [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds. */ - kind?: string; + get kind(): string | undefined { + return this._foldingRangeKind + } + + get openWord(): string { + return this._openWord ?? ''; + } - constructor(range: Range, foldingRangeKind?: FoldingRangeKind) { - this.startLine = range.start.line; - this.endLine = range.end.line; - this.kind = foldingRangeKind; + get closeWord(): string { + return this._closeWord ?? ''; } + + get range() { + return { + startLine: this.startLine, + endLine: this.endLine + } + } + + constructor( + private _range: Range, + private _foldingRangeKind?: FoldingRangeKind, + private _openWord?: string, + private _closeWord?: string) { } } \ No newline at end of file diff --git a/server/src/project/workspace.ts b/server/src/project/workspace.ts index f6e3fa2..9502010 100644 --- a/server/src/project/workspace.ts +++ b/server/src/project/workspace.ts @@ -345,9 +345,9 @@ class WorkspaceEvents { } const result = document?.languageServerFoldingRanges(); for (const foldingRange of result ?? []) { - Services.logger.debug(`${JSON.stringify(foldingRange)}`, 1); + Services.logger.debug(`${JSON.stringify(foldingRange.range)} '${foldingRange.openWord}..${foldingRange.closeWord}'`, 1); } - return result ?? []; + return result?.map(x => x.range) ?? []; } private onHover(params: HoverParams): Hover { From aa720945f28b94bd12fbc5c077afc6fa94ab148c Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:01:23 +0800 Subject: [PATCH 07/10] New folding range tests --- client/src/test/foldingRanges.test.ts | 35 ++++++++++++++ client/testFixture/FoldingRanges.bas | 67 +++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 client/src/test/foldingRanges.test.ts create mode 100644 client/testFixture/FoldingRanges.bas diff --git a/client/src/test/foldingRanges.test.ts b/client/src/test/foldingRanges.test.ts new file mode 100644 index 0000000..45f47c6 --- /dev/null +++ b/client/src/test/foldingRanges.test.ts @@ -0,0 +1,35 @@ +import * as vscode from 'vscode'; +import * as assert from 'assert'; +import { getDocUri, activate } from './helper'; + +suite('Should get text edits', () => { + test('formatting.class.template', async () => { + const subFoo = {start: 23, end: 42}; + const subBar = {start: 44, end: 58}; + const ifBlockOuter = {start: 33, end: 41}; + const ifBlockInner = {start: 34, end: 36}; + const whileWend = {start: 54, end: 57}; + + await testFoldingRanges(getDocUri('FormatTemplateClass.cls'), [ + subFoo, + subBar, + whileWend + ]); + }); +}); + +async function testFoldingRanges(docUri: vscode.Uri, expectedFoldingRanges: vscode.FoldingRange[]) { + await activate(docUri); + const actualFoldingRanges = await vscode.commands.executeCommand( + 'vscode.executeFormatDocumentProvider', + docUri, + { tabSize: 4, insertSpaces: true } + ) + + assert.equal(actualFoldingRanges.length ?? 0, expectedFoldingRanges.length, "Count"); + + expectedFoldingRanges.forEach((expectedFoldingRange, i) => { + const actualFoldingRange = actualFoldingRanges[i]; + assert.deepEqual(actualFoldingRange, expectedFoldingRange, `FoldingRange ${i}`); + }); +} \ No newline at end of file diff --git a/client/testFixture/FoldingRanges.bas b/client/testFixture/FoldingRanges.bas new file mode 100644 index 0000000..8fe9b8e --- /dev/null +++ b/client/testFixture/FoldingRanges.bas @@ -0,0 +1,67 @@ +Attribute VB_Name = "FoldingRanges" +' Copyright 2024 Sam Vanderslink +' +' Permission is hereby granted, free of charge, to any person obtaining a copy +' of this software and associated documentation files (the "Software"), to deal +' in the Software without restriction, including without limitation the rights +' to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +' copies of the Software, and to permit persons to whom the Software is +' furnished to do so, subject to the following conditions: +' +' The above copyright notice and this permission notice shall be included in +' all copies or substantial portions of the Software. +' +' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +' FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +' IN THE SOFTWARE. + +Option Explicit + +Public Sub Foo() +Attribute Foo.VB_Description = "Tests a folding range." +' Tests a folding range. +' +' Args: +' param1: +' +' Raises: +' + + If FoldingRanges Then + If FoldingRanges Then + Debug.Print "Great!" + End If + ElseIf UnfoldingRanges Then + Debug.Print "What does this even mean?" + Else + Debug.Print "Not great..." + End If +End Sub + +Public Sub Bar() +Attribute Bar.VB_Description = "Tests more folding ranges." +' Tests more folding ranges. +' +' Args: +' param1: +' +' Raises: +' + + While True + Debug.Print "You ain't never going home!" + DoEvents + Wend +End Sub + + +#If VBA7 Then + Public Function FooBar() As LongPtr +#Else + Public Function FooBar() As Long +#End If + End Function \ No newline at end of file From 06fad70b0a63ee3589d49bc8ac553d207ab1ccd9 Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:02:51 +0800 Subject: [PATCH 08/10] Added folding range capability --- server/src/project/elements/flow.ts | 3 ++- server/src/project/elements/precompiled.ts | 5 ++++- server/src/project/elements/procedure.ts | 15 ++++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/server/src/project/elements/flow.ts b/server/src/project/elements/flow.ts index 25af6bf..ff6e8f1 100644 --- a/server/src/project/elements/flow.ts +++ b/server/src/project/elements/flow.ts @@ -5,7 +5,7 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; import { AnyOperatorContext, WhileStatementContext } from '../../antlr/out/vbaParser'; // Project -import { DiagnosticCapability } from '../../capabilities/capabilities'; +import { DiagnosticCapability, FoldingRangeCapability } from '../../capabilities/capabilities'; import { BaseContextSyntaxElement, HasDiagnosticCapability } from './base'; import { MultipleOperatorsDiagnostic, WhileWendDeprecatedDiagnostic } from '../../capabilities/diagnostics'; @@ -15,6 +15,7 @@ export class WhileLoopElement extends BaseContextSyntaxElement { this.diagnosticCapability.diagnostics.push(new WhileWendDeprecatedDiagnostic(this.context.range)); return this.diagnosticCapability.diagnostics; diff --git a/server/src/project/elements/precompiled.ts b/server/src/project/elements/precompiled.ts index ff5a892..6d01ef9 100644 --- a/server/src/project/elements/precompiled.ts +++ b/server/src/project/elements/precompiled.ts @@ -5,7 +5,7 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; import { CompilerConditionalBlockContext, CompilerDefaultBlockContext, CompilerIfBlockContext } from '../../antlr/out/vbapreParser'; // Project -import { DiagnosticCapability } from '../../capabilities/capabilities'; +import { DiagnosticCapability, FoldingRangeCapability } from '../../capabilities/capabilities'; import { BaseContextSyntaxElement } from '../elements/base'; import { UnreachableCodeDiagnostic } from '../../capabilities/diagnostics'; @@ -16,6 +16,9 @@ export class CompilerLogicalBlock extends BaseContextSyntaxElement extends BaseContextSyntaxElement implements HasDiagnosticCapability, HasSymbolInformationCapability { diagnosticCapability: DiagnosticCapability; + foldingRangeCapability: FoldingRangeCapability; symbolInformationCapability: SymbolInformationCapability; abstract identifierCapability: IdentifierCapability; constructor(ctx: T, doc: TextDocument, symbolKind: SymbolKind) { super(ctx, doc); this.diagnosticCapability = new DiagnosticCapability(this); + this.foldingRangeCapability = new FoldingRangeCapability(this); this.symbolInformationCapability = new SymbolInformationCapability(this, symbolKind); } } @@ -39,6 +41,9 @@ export class SubDeclarationElement extends BaseProcedureElement ctx.subroutineName()?.ambiguousIdentifier(), }); + this.foldingRangeCapability.openWord = `Sub ${this.identifierCapability.name}`; + this.foldingRangeCapability.closeWord = 'End Sub'; + } } @@ -52,6 +57,8 @@ export class FunctionDeclarationElement extends BaseProcedureElement ctx.functionName()?.ambiguousIdentifier(), }); + this.foldingRangeCapability.openWord = `Function ${this.identifierCapability.name}`; + this.foldingRangeCapability.closeWord = 'End Function'; } } @@ -100,6 +107,8 @@ abstract class BasePropertyDeclarationElement exten export class PropertyGetDeclarationElement extends BasePropertyDeclarationElement { constructor(ctx: PropertyGetDeclarationContext, doc: TextDocument) { super(ctx, doc, 'Get', ctx.functionName()?.ambiguousIdentifier() ?? undefined); + this.foldingRangeCapability.openWord = `Get Property ${this.identifierCapability.name}`; + this.foldingRangeCapability.closeWord = 'End Property'; } } @@ -107,6 +116,8 @@ export class PropertyGetDeclarationElement extends BasePropertyDeclarationElemen export class PropertySetDeclarationElement extends BasePropertyDeclarationElement { constructor(ctx: PropertySetDeclarationContext, doc: TextDocument) { super(ctx, doc, 'Set', ctx.subroutineName()?.ambiguousIdentifier() ?? undefined); + this.foldingRangeCapability.openWord = `Set Property ${this.identifierCapability.name}`; + this.foldingRangeCapability.closeWord = 'End Property'; } } @@ -114,5 +125,7 @@ export class PropertySetDeclarationElement extends BasePropertyDeclarationElemen export class PropertyLetDeclarationElement extends BasePropertyDeclarationElement { constructor(ctx: PropertySetDeclarationContext, doc: TextDocument) { super(ctx, doc, 'Let', ctx.subroutineName()?.ambiguousIdentifier() ?? undefined); + this.foldingRangeCapability.openWord = `Let Property ${this.identifierCapability.name}`; + this.foldingRangeCapability.closeWord = 'End Property'; } } \ No newline at end of file From 93ebc0a34861d6c9977fa6b4db19066f17d341a4 Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:15:48 +0800 Subject: [PATCH 09/10] Add folding range capability --- client/src/test/foldingRanges.test.ts | 2 ++ server/src/project/elements/flow.ts | 13 ++++++++++++- server/src/project/parser/vbaListener.ts | 6 +++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/client/src/test/foldingRanges.test.ts b/client/src/test/foldingRanges.test.ts index 45f47c6..4f23ed8 100644 --- a/client/src/test/foldingRanges.test.ts +++ b/client/src/test/foldingRanges.test.ts @@ -12,6 +12,8 @@ suite('Should get text edits', () => { await testFoldingRanges(getDocUri('FormatTemplateClass.cls'), [ subFoo, + ifBlockOuter, + ifBlockInner, subBar, whileWend ]); diff --git a/server/src/project/elements/flow.ts b/server/src/project/elements/flow.ts index ff6e8f1..4ffd556 100644 --- a/server/src/project/elements/flow.ts +++ b/server/src/project/elements/flow.ts @@ -2,13 +2,22 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; // Antlr -import { AnyOperatorContext, WhileStatementContext } from '../../antlr/out/vbaParser'; +import { AnyOperatorContext, IfStatementContext, WhileStatementContext } from '../../antlr/out/vbaParser'; // Project import { DiagnosticCapability, FoldingRangeCapability } from '../../capabilities/capabilities'; import { BaseContextSyntaxElement, HasDiagnosticCapability } from './base'; import { MultipleOperatorsDiagnostic, WhileWendDeprecatedDiagnostic } from '../../capabilities/diagnostics'; +export class IfElseBlock extends BaseContextSyntaxElement { + constructor(context: IfStatementContext, document: TextDocument) { + super(context, document); + this.foldingRangeCapability = new FoldingRangeCapability(this); + this.foldingRangeCapability.openWord = 'If'; + this.foldingRangeCapability.closeWord = 'End If'; + } +} + export class WhileLoopElement extends BaseContextSyntaxElement implements HasDiagnosticCapability { diagnosticCapability: DiagnosticCapability; @@ -16,6 +25,8 @@ export class WhileLoopElement extends BaseContextSyntaxElement { this.diagnosticCapability.diagnostics.push(new WhileWendDeprecatedDiagnostic(this.context.range)); return this.diagnosticCapability.diagnostics; diff --git a/server/src/project/parser/vbaListener.ts b/server/src/project/parser/vbaListener.ts index 33eb41f..217e490 100644 --- a/server/src/project/parser/vbaListener.ts +++ b/server/src/project/parser/vbaListener.ts @@ -13,6 +13,7 @@ import { EnumDeclarationContext, FunctionDeclarationContext, GlobalVariableDeclarationContext, + IfStatementContext, IgnoredClassAttrContext, IgnoredProceduralAttrContext, PrivateConstDeclarationContext, @@ -55,7 +56,7 @@ import { // Project import { CompilerLogicalBlock } from '../elements/precompiled'; import { UnexpectedEndOfLineElement } from '../elements/utils'; -import { DuplicateOperatorElement, WhileLoopElement } from '../elements/flow'; +import { DuplicateOperatorElement, IfElseBlock as IfStatementElement, WhileLoopElement } from '../elements/flow'; import { VbaClassDocument, VbaModuleDocument } from '../document'; import { ClassElement, ModuleElement, ModuleIgnoredAttributeElement } from '../elements/module'; import { DeclarationStatementElement, EnumDeclarationElement, TypeDeclarationElement, TypeSuffixElement } from '../elements/typing'; @@ -128,6 +129,9 @@ export class VbaListener extends vbaListener { exitClassModule = (_: ClassModuleContext) => this.document.deregisterNamespaceElement(); + enterIfStatement = (ctx: IfStatementContext) => + this.document.registerElement(new IfStatementElement(ctx, this.document.textDocument)); + enterIgnoredClassAttr = (ctx: IgnoredClassAttrContext) => this.registerIgnoredAttribute(ctx); enterIgnoredProceduralAttr = (ctx: IgnoredProceduralAttrContext) => this.registerIgnoredAttribute(ctx); private registerIgnoredAttribute(ctx: IgnoredClassAttrContext | IgnoredProceduralAttrContext) { From 774b57751f88a685afc996b0db1f8a250719546e Mon Sep 17 00:00:00 2001 From: sslinky <39886505+SSlinky@users.noreply.github.com> Date: Fri, 4 Apr 2025 12:46:20 +0800 Subject: [PATCH 10/10] Fix folding range tests --- client/src/test/foldingRanges.test.ts | 11 ++++++----- client/testFixture/FoldingRanges.bas | 9 +++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/client/src/test/foldingRanges.test.ts b/client/src/test/foldingRanges.test.ts index 4f23ed8..9c444b9 100644 --- a/client/src/test/foldingRanges.test.ts +++ b/client/src/test/foldingRanges.test.ts @@ -9,13 +9,15 @@ suite('Should get text edits', () => { const ifBlockOuter = {start: 33, end: 41}; const ifBlockInner = {start: 34, end: 36}; const whileWend = {start: 54, end: 57}; + const propFoobar = {start: 61, end: 63}; - await testFoldingRanges(getDocUri('FormatTemplateClass.cls'), [ + await testFoldingRanges(getDocUri('FoldingRanges.bas'), [ subFoo, ifBlockOuter, ifBlockInner, subBar, - whileWend + whileWend, + propFoobar ]); }); }); @@ -23,9 +25,8 @@ suite('Should get text edits', () => { async function testFoldingRanges(docUri: vscode.Uri, expectedFoldingRanges: vscode.FoldingRange[]) { await activate(docUri); const actualFoldingRanges = await vscode.commands.executeCommand( - 'vscode.executeFormatDocumentProvider', - docUri, - { tabSize: 4, insertSpaces: true } + 'vscode.executeFoldingRangeProvider', + docUri ) assert.equal(actualFoldingRanges.length ?? 0, expectedFoldingRanges.length, "Count"); diff --git a/client/testFixture/FoldingRanges.bas b/client/testFixture/FoldingRanges.bas index 8fe9b8e..fd586cb 100644 --- a/client/testFixture/FoldingRanges.bas +++ b/client/testFixture/FoldingRanges.bas @@ -59,9 +59,6 @@ Attribute Bar.VB_Description = "Tests more folding ranges." End Sub -#If VBA7 Then - Public Function FooBar() As LongPtr -#Else - Public Function FooBar() As Long -#End If - End Function \ No newline at end of file +Public Function FooBar() As Long + +End Function \ No newline at end of file