From 225199956d8995565e6b1470b2aba35cdb53acda Mon Sep 17 00:00:00 2001 From: Colen Garoutte-Carson Date: Tue, 14 Jul 2026 11:38:07 -0700 Subject: [PATCH 1/2] Support requests of multiple custom configs --- Extension/src/LanguageServer/client.ts | 60 +++++++++++++------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index 3410e7502..c8e16f3dd 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -486,8 +486,14 @@ interface CodeAnalysisParams { scope: CodeAnalysisScope; } +interface RequestCustomConfigsParams { + workspaceFolderUri: string; + files: string[]; + batchId: number; +} + interface FinishedRequestCustomConfigParams { - uri: string; + batchId: number; isProviderRegistered: boolean; } @@ -651,7 +657,7 @@ const ClearCustomConfigurationsNotification: NotificationType = new NotificationType('cpptools/clearCustomBrowseConfiguration'); const PreviewReferencesNotification: NotificationType = new NotificationType('cpptools/previewReferences'); const RescanFolderNotification: NotificationType = new NotificationType('cpptools/rescanFolder'); -const FinishedRequestCustomConfig: NotificationType = new NotificationType('cpptools/finishedRequestCustomConfig'); +const FinishedRequestCustomConfig: NotificationType = new NotificationType('cpptools/finishedRequestCustomConfigs'); const DidChangeSettingsNotification: NotificationType = new NotificationType('cpptools/didChangeSettings'); const DidChangeVisibleTextEditorsNotification: NotificationType = new NotificationType('cpptools/didChangeVisibleTextEditors'); const DidChangeTextEditorVisibleRangesNotification: NotificationType = new NotificationType('cpptools/didChangeTextEditorVisibleRanges'); @@ -674,7 +680,7 @@ const DebugLogNotification: NotificationType = new Notific const CompileCommandsPathsNotification: NotificationType = new NotificationType('cpptools/compileCommandsPaths'); const ReferencesNotification: NotificationType = new NotificationType('cpptools/references'); const ReportReferencesProgressNotification: NotificationType = new NotificationType('cpptools/reportReferencesProgress'); -const RequestCustomConfig: NotificationType = new NotificationType('cpptools/requestCustomConfig'); +const RequestCustomConfigs: NotificationType = new NotificationType('cpptools/requestCustomConfigs'); const PublishRefactorDiagnosticsNotification: NotificationType = new NotificationType('cpptools/publishRefactorDiagnostics'); const ShowMessageWindowNotification: NotificationType = new NotificationType('cpptools/showMessageWindow'); const ShowWarningNotification: NotificationType = new NotificationType('cpptools/showWarning'); @@ -804,7 +810,7 @@ export interface Client { onRegisterCustomConfigurationProvider(provider: CustomConfigurationProvider1): Thenable; updateCustomConfigurations(requestingProvider?: CustomConfigurationProvider1): Thenable; updateCustomBrowseConfiguration(requestingProvider?: CustomConfigurationProvider1): Thenable; - provideCustomConfiguration(docUri: vscode.Uri): Promise; + provideCustomConfigurations(docUris: vscode.Uri[], batchId: number): Promise; logDiagnostics(): Promise; rescanFolder(): Promise; toggleReferenceResultsView(): void; @@ -2274,10 +2280,10 @@ export class DefaultClient implements Client { return this.languageClient.sendNotification(RescanFolderNotification); } - public async provideCustomConfiguration(docUri: vscode.Uri): Promise { + public async provideCustomConfigurations(docUris: vscode.Uri[], batchId: number): Promise { let isProviderRegistered: boolean = false; const onFinished: () => void = () => { - void this.languageClient.sendNotification(FinishedRequestCustomConfig, { uri: docUri.toString(), isProviderRegistered }); + void this.languageClient.sendNotification(FinishedRequestCustomConfig, { batchId, isProviderRegistered }); }; try { const providerId: string | undefined = this.configurationProvider; @@ -2289,29 +2295,22 @@ export class DefaultClient implements Client { return; } isProviderRegistered = true; - const resultCode = await this.provideCustomConfigurationAsync(docUri, provider); - telemetry.logLanguageServerEvent('provideCustomConfiguration', { providerId, resultCode }); + const resultCode = await this.provideCustomConfigurationAsync(docUris, provider); + telemetry.logLanguageServerEvent('provideCustomConfigurations', { providerId, resultCode }); } finally { onFinished(); } } - private async provideCustomConfigurationAsync(docUri: vscode.Uri, provider: CustomConfigurationProvider1): Promise { + private async provideCustomConfigurationAsync(docUris: vscode.Uri[], provider: CustomConfigurationProvider1): Promise { const tokenSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource(); // Need to loop through candidates, to see if we can get a custom configuration from any of them. // Wrap all lookups in a single task, so we can apply a timeout to the entire duration. const provideConfigurationAsync: () => Thenable = async () => { - try { - if (!await provider.canProvideConfiguration(docUri, tokenSource.token)) { - return []; - } - } catch { - console.warn("Caught exception from canProvideConfiguration"); - } let configs: util.Mutable[] = []; try { - configs = await provider.provideConfigurations([docUri], tokenSource.token); + configs = await provider.provideConfigurations(docUris, tokenSource.token); } catch { console.warn("Caught exception from provideConfigurations"); } @@ -2367,7 +2366,7 @@ export class DefaultClient implements Client { } catch (err) { result = "timeout"; const settings: CppSettings = new CppSettings(this.RootUri); - if (settings.isConfigurationWarningsEnabled && !this.isExternalHeader(docUri) && !vscode.debug.activeDebugSession) { + if (settings.isConfigurationWarningsEnabled && !vscode.debug.activeDebugSession) { const dismiss: string = localize("dismiss.button", "Dismiss"); const disable: string = localize("disable.warnings.button", "Disable Warnings"); const configName: string | undefined = this.configuration.CurrentConfiguration?.name; @@ -2375,8 +2374,8 @@ export class DefaultClient implements Client { return "noConfigName"; } let message: string = localize("unable.to.provide.configuration", - "{0} is unable to provide IntelliSense configuration information for '{1}'. Settings from the '{2}' configuration will be used instead.", - provider.name, docUri.fsPath, configName); + "{0} is unable to provide IntelliSense configuration information. Settings from the '{1}' configuration will be used instead.", + provider.name, configName); if (err) { message += ` (${err})`; } @@ -2392,20 +2391,19 @@ export class DefaultClient implements Client { return result; } - private handleRequestCustomConfig(file: string): void { - const uri: vscode.Uri = vscode.Uri.file(file); - const client: Client = clients.getClientFor(uri); + private handleRequestCustomConfigs(params: RequestCustomConfigsParams): void { + const workspaceFolderUri: vscode.Uri = vscode.Uri.parse(params.workspaceFolderUri); + const client: Client = clients.getClientFor(workspaceFolderUri); + if (!client) { + return; + } if (client instanceof DefaultClient) { const defaultClient: DefaultClient = client as DefaultClient; - void defaultClient.provideCustomConfiguration(uri).catch(logAndReturn.undefined); + const uris: vscode.Uri[] = params.files.map(file => vscode.Uri.file(file)); + void defaultClient.provideCustomConfigurations(uris, params.batchId).catch(logAndReturn.undefined); } } - private isExternalHeader(uri: vscode.Uri): boolean { - const rootUri: vscode.Uri | undefined = this.RootUri; - return !rootUri || (util.isHeaderFile(uri) && !uri.toString().startsWith(rootUri.toString())); - } - public async getCurrentConfigName(): Promise { await this.ready; return this.configuration.CurrentConfiguration?.name; @@ -2649,7 +2647,7 @@ export class DefaultClient implements Client { this.languageClient.onNotification(CompileCommandsPathsNotification, (e) => void this.promptCompileCommands(e)); this.languageClient.onNotification(ReferencesNotification, (e) => this.processReferencesPreview(e)); this.languageClient.onNotification(ReportReferencesProgressNotification, (e) => this.handleReferencesProgress(e)); - this.languageClient.onNotification(RequestCustomConfig, (e) => this.handleRequestCustomConfig(e)); + this.languageClient.onNotification(RequestCustomConfigs, (e) => this.handleRequestCustomConfigs(e)); this.languageClient.onNotification(IntelliSenseResultNotification, (e) => this.handleIntelliSenseResult(e)); this.languageClient.onNotification(PublishRefactorDiagnosticsNotification, publishRefactorDiagnostics); RegisterCodeAnalysisNotifications(this.languageClient); @@ -4458,7 +4456,7 @@ class NullClient implements Client { onRegisterCustomConfigurationProvider(provider: CustomConfigurationProvider1): Thenable { return Promise.resolve(); } updateCustomConfigurations(requestingProvider?: CustomConfigurationProvider1): Thenable { return Promise.resolve(); } updateCustomBrowseConfiguration(requestingProvider?: CustomConfigurationProvider1): Thenable { return Promise.resolve(); } - provideCustomConfiguration(docUri: vscode.Uri): Promise { return Promise.resolve(); } + provideCustomConfigurations(docUris: vscode.Uri[], batchId: number): Promise { return Promise.resolve(); } logDiagnostics(): Promise { return Promise.resolve(); } rescanFolder(): Promise { return Promise.resolve(); } toggleReferenceResultsView(): void { } From 37533728fe69a43a001f85695d8df3164bb6fc8f Mon Sep 17 00:00:00 2001 From: Colen Garoutte-Carson Date: Tue, 14 Jul 2026 12:27:45 -0700 Subject: [PATCH 2/2] Address PR feedback --- Extension/src/LanguageServer/client.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index c8e16f3dd..0c522c1ee 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -2305,8 +2305,7 @@ export class DefaultClient implements Client { private async provideCustomConfigurationAsync(docUris: vscode.Uri[], provider: CustomConfigurationProvider1): Promise { const tokenSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource(); - // Need to loop through candidates, to see if we can get a custom configuration from any of them. - // Wrap all lookups in a single task, so we can apply a timeout to the entire duration. + // Wrap the provider lookup in a single task, so we can apply a timeout to the entire duration. const provideConfigurationAsync: () => Thenable = async () => { let configs: util.Mutable[] = []; try { @@ -2357,7 +2356,13 @@ export class DefaultClient implements Client { }; let result: string = "success"; try { - const configs: SourceFileConfigurationItem[] | undefined = await this.callTaskWithTimeout(provideConfigurationAsync, configProviderTimeout, tokenSource); + let configs: SourceFileConfigurationItem[] | undefined; + // Multi-file requests are async, and do not require a timeout. + if (docUris.length > 1) { + configs = await provideConfigurationAsync(); + } else { + configs = await this.callTaskWithTimeout(provideConfigurationAsync, configProviderTimeout, tokenSource); + } if (configs && configs.length > 0) { this.sendCustomConfigurations(configs, provider.version); } else { @@ -2394,9 +2399,6 @@ export class DefaultClient implements Client { private handleRequestCustomConfigs(params: RequestCustomConfigsParams): void { const workspaceFolderUri: vscode.Uri = vscode.Uri.parse(params.workspaceFolderUri); const client: Client = clients.getClientFor(workspaceFolderUri); - if (!client) { - return; - } if (client instanceof DefaultClient) { const defaultClient: DefaultClient = client as DefaultClient; const uris: vscode.Uri[] = params.files.map(file => vscode.Uri.file(file));