diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index 680c14749..30873ee1a 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -805,6 +805,7 @@ export interface Client { setCurrentConfigName(configurationName: string): Thenable; getCurrentConfigName(): Thenable; getCurrentConfigCustomVariable(variableName: string): Thenable; + waitForTagParsing(timeout: number, token: vscode.CancellationToken): Promise; getVcpkgInstalled(): Thenable; getVcpkgEnabled(): Thenable; getCurrentCompilerPathAndArgs(): Thenable; @@ -919,6 +920,7 @@ export class DefaultClient implements Client { private configStateReceived: ConfigStateReceived = { compilers: false, compileCommands: false, configProviders: undefined, timeout: false }; private showConfigureIntelliSenseButton: boolean = false; + private pendingTagParsingCalls: { promise: ManualPromise; timer?: NodeJS.Timeout; cancellationListener?: vscode.Disposable }[] = []; /** A queue of asynchronous tasks that need to be processed befofe ready is considered active. */ private static queue = new Array<[ManualPromise, () => Promise] | [ManualPromise]>(); @@ -1008,6 +1010,64 @@ export class DefaultClient implements Client { }; } + // If there are any pending calls that were waiting for tag parsing to complete, we can resolve them since it's finished. If there are no pending calls, this does nothing. + private resolvePendingTagParsingCallsIfReady(): void { + if (!this.pendingTagParsingCalls.length || this.IsTagParsing) { + return; + } + + const pendingCalls: { promise: ManualPromise; timer?: NodeJS.Timeout; cancellationListener?: vscode.Disposable }[] = this.pendingTagParsingCalls; + this.pendingTagParsingCalls = []; + pendingCalls.forEach(pendingCall => { + if (pendingCall.timer) { + clearTimeout(pendingCall.timer); + } + pendingCall.cancellationListener?.dispose(); + pendingCall.promise.resolve(true); + }); + } + + public async waitForTagParsing(timeout: number, token: vscode.CancellationToken): Promise { + // On initialization, the client has UI bools all set to false which could cause an early return. We want to ensure it's ready first. + await this.ready; + + if (!this.IsTagParsing) { + return true; + } + + if (token.isCancellationRequested) { + throw new vscode.CancellationError(); + } + + const pendingCall: { promise: ManualPromise; timer?: NodeJS.Timeout; cancellationListener?: vscode.Disposable } = { + promise: new ManualPromise() + }; + + pendingCall.timer = global.setTimeout(() => { + const index: number = this.pendingTagParsingCalls.indexOf(pendingCall); + if (index !== -1) { + this.pendingTagParsingCalls.splice(index, 1); + } + pendingCall.cancellationListener?.dispose(); + pendingCall.promise.resolve(false); + }, timeout); + + pendingCall.cancellationListener = token.onCancellationRequested(() => { + const index: number = this.pendingTagParsingCalls.indexOf(pendingCall); + if (index !== -1) { + this.pendingTagParsingCalls.splice(index, 1); + } + if (pendingCall.timer) { + clearTimeout(pendingCall.timer); + } + pendingCall.cancellationListener?.dispose(); + pendingCall.promise.reject(new vscode.CancellationError()); + }); + + this.pendingTagParsingCalls.push(pendingCall); + return pendingCall.promise; + } + private getName(workspaceFolder?: vscode.WorkspaceFolder): string { return workspaceFolder ? workspaceFolder.name : "untitled"; } @@ -2893,6 +2953,8 @@ export class DefaultClient implements Client { } else if (message.includes("/")) { this.lastInvokedLspMessage = message; } + + this.resolvePendingTagParsingCallsIfReady(); } private updateTagParseStatus(tagParseStatus: TagParseStatus): void { @@ -4208,6 +4270,14 @@ export class DefaultClient implements Client { } public dispose(): void { + this.pendingTagParsingCalls.forEach(pendingCall => { + if (pendingCall.timer) { + clearTimeout(pendingCall.timer); + } + pendingCall.cancellationListener?.dispose(); + pendingCall.promise.resolve(false); + }); + this.pendingTagParsingCalls = []; this.disposables.forEach((d) => d.dispose()); this.disposables = []; if (this.documentFormattingProviderDisposable) { @@ -4360,6 +4430,7 @@ class NullClient implements Client { setCurrentConfigName(configurationName: string): Thenable { return Promise.resolve(); } getCurrentConfigName(): Thenable { return Promise.resolve(""); } getCurrentConfigCustomVariable(variableName: string): Thenable { return Promise.resolve(""); } + waitForTagParsing(timeout: number, token: vscode.CancellationToken): Promise { return token.isCancellationRequested ? Promise.reject(new vscode.CancellationError()) : Promise.resolve(true); } getVcpkgInstalled(): Thenable { return Promise.resolve(false); } getVcpkgEnabled(): Thenable { return Promise.resolve(false); } getCurrentCompilerPathAndArgs(): Thenable { return Promise.resolve(undefined); } diff --git a/Extension/src/LanguageServer/extension.ts b/Extension/src/LanguageServer/extension.ts index 79f89f9d6..6af3223e5 100644 --- a/Extension/src/LanguageServer/extension.ts +++ b/Extension/src/LanguageServer/extension.ts @@ -430,6 +430,7 @@ export async function registerCommands(enabled: boolean): Promise { commandDisposables.push(vscode.commands.registerCommand('cpptools.activeConfigName', enabled ? onGetActiveConfigName : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('cpptools.activeConfigCustomVariable', enabled ? onGetActiveConfigCustomVariable : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('cpptools.setActiveConfigName', enabled ? onSetActiveConfigName : onDisabledCommand)); + commandDisposables.push(vscode.commands.registerCommand('C_Cpp.waitForTagParsing', enabled ? onWaitForTagParsing : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.RestartIntelliSenseForFile', enabled ? onRestartIntelliSenseForFile : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.GenerateDoxygenComment', enabled ? onGenerateDoxygenComment : onDisabledCommand)); commandDisposables.push(vscode.commands.registerCommand('C_Cpp.CreateDeclarationOrDefinition', enabled ? onCreateDeclarationOrDefinition : onDisabledCommand)); @@ -977,6 +978,10 @@ function onGetActiveConfigCustomVariable(variableName: string): Thenable return clients.ActiveClient.getCurrentConfigCustomVariable(variableName); } +async function onWaitForTagParsing(timeout: number, token: vscode.CancellationToken): Promise { + return clients.getDefaultClient().waitForTagParsing(timeout, token); +} + function onLogDiagnostics(): Promise { return clients.ActiveClient.logDiagnostics(); }