diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index 30d016d33..481aa9499 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -246,15 +246,6 @@ interface CompileCommandsPaths extends WorkspaceFolderParams { paths: string[]; } -interface QueryTranslationUnitSourceParams extends WorkspaceFolderParams { - uri: string; - ignoreExisting: boolean; -} - -interface QueryTranslationUnitSourceResult { - candidates: string[]; -} - interface GetDiagnosticsResult { diagnostics: string; } @@ -554,7 +545,6 @@ export interface ChatContextResult { const PreInitializationRequest: RequestType = new RequestType('cpptools/preinitialize'); const InitializationRequest: RequestType = new RequestType('cpptools/initialize'); const QueryCompilerDefaultsRequest: RequestType = new RequestType('cpptools/queryCompilerDefaults'); -const QueryTranslationUnitSourceRequest: RequestType = new RequestType('cpptools/queryTranslationUnitSource'); const SwitchHeaderSourceRequest: RequestType = new RequestType('cpptools/didSwitchHeaderSource'); const GetDiagnosticsRequest: RequestType = new RequestType('cpptools/getDiagnostics'); export const GetDocumentSymbolRequest: RequestType = new RequestType('cpptools/getDocumentSymbols'); @@ -744,7 +734,7 @@ export interface Client { onRegisterCustomConfigurationProvider(provider: CustomConfigurationProvider1): Thenable; updateCustomConfigurations(requestingProvider?: CustomConfigurationProvider1): Thenable; updateCustomBrowseConfiguration(requestingProvider?: CustomConfigurationProvider1): Thenable; - provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean): Promise; + provideCustomConfiguration(docUri: vscode.Uri): Promise; logDiagnostics(): Promise; rescanFolder(): Promise; toggleReferenceResultsView(): void; @@ -1871,9 +1861,6 @@ export class DefaultClient implements Client { } await this.clearCustomConfigurations(); - await Promise.all([ - ...[...this.trackedDocuments].map(([_uri, document]) => this.provideCustomConfiguration(document.uri, undefined, true)) - ]); } public async updateCustomBrowseConfiguration(requestingProvider?: CustomConfigurationProvider1): Promise { @@ -2023,68 +2010,42 @@ export class DefaultClient implements Client { return this.languageClient.sendNotification(RescanFolderNotification); } - public async provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean): Promise { + public async provideCustomConfiguration(docUri: vscode.Uri): Promise { const onFinished: () => void = () => { - if (requestFile) { - void this.languageClient.sendNotification(FinishedRequestCustomConfig, { uri: requestFile }); - } + void this.languageClient.sendNotification(FinishedRequestCustomConfig, { uri: docUri.toString() }); }; - const providerId: string | undefined = this.configurationProvider; - if (!providerId) { - onFinished(); - return; - } - const provider: CustomConfigurationProvider1 | undefined = getCustomConfigProviders().get(providerId); - if (!provider || !provider.isReady) { - onFinished(); - return; - } try { - DefaultClient.isStarted.reset(); - const resultCode = await this.provideCustomConfigurationAsync(docUri, requestFile, replaceExisting, provider); + const providerId: string | undefined = this.configurationProvider; + if (!providerId) { + return; + } + const provider: CustomConfigurationProvider1 | undefined = getCustomConfigProviders().get(providerId); + if (!provider || !provider.isReady) { + return; + } + const resultCode = await this.provideCustomConfigurationAsync(docUri, provider); telemetry.logLanguageServerEvent('provideCustomConfiguration', { providerId, resultCode }); } finally { onFinished(); - DefaultClient.isStarted.resolve(); } } - private async provideCustomConfigurationAsync(docUri: vscode.Uri, requestFile: string | undefined, replaceExisting: boolean | undefined, provider: CustomConfigurationProvider1): Promise { + private async provideCustomConfigurationAsync(docUri: vscode.Uri, provider: CustomConfigurationProvider1): Promise { const tokenSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource(); - const params: QueryTranslationUnitSourceParams = { - uri: docUri.toString(), - ignoreExisting: !!replaceExisting, - workspaceFolderUri: this.RootUri?.toString() - }; - - const response: QueryTranslationUnitSourceResult = await this.languageClient.sendRequest(QueryTranslationUnitSourceRequest, params); - if (!response.candidates || response.candidates.length === 0) { - // If we didn't receive any candidates, no configuration is needed. - return "noCandidates"; - } - // 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 () => { - const uris: vscode.Uri[] = []; - for (let i: number = 0; i < response.candidates.length; ++i) { - const candidate: string = response.candidates[i]; - const tuUri: vscode.Uri = vscode.Uri.parse(candidate); - try { - if (await provider.canProvideConfiguration(tuUri, tokenSource.token)) { - uris.push(tuUri); - } - } catch (err) { - console.warn("Caught exception from canProvideConfiguration"); + try { + if (!await provider.canProvideConfiguration(docUri, tokenSource.token)) { + return []; } - } - if (!uris.length) { - return []; + } catch (err) { + console.warn("Caught exception from canProvideConfiguration"); } let configs: util.Mutable[] = []; try { - configs = await provider.provideConfigurations(uris, tokenSource.token); + configs = await provider.provideConfigurations([docUri], tokenSource.token); } catch (err) { console.warn("Caught exception from provideConfigurations"); } @@ -2132,39 +2093,42 @@ export class DefaultClient implements Client { try { const configs: SourceFileConfigurationItem[] | undefined = await this.callTaskWithTimeout(provideConfigurationAsync, configProviderTimeout, tokenSource); if (configs && configs.length > 0) { - this.sendCustomConfigurations(configs, provider.version, requestFile !== undefined); + this.sendCustomConfigurations(configs, provider.version); } else { result = "noConfigurations"; } } catch (err) { result = "timeout"; - if (!requestFile) { - const settings: CppSettings = new CppSettings(this.RootUri); - if (settings.isConfigurationWarningsEnabled && !this.isExternalHeader(docUri) && !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; - if (!configName) { - 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); - if (err) { - message += ` (${err})`; - } + const settings: CppSettings = new CppSettings(this.RootUri); + if (settings.isConfigurationWarningsEnabled && !this.isExternalHeader(docUri) && !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; + if (!configName) { + 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); + if (err) { + message += ` (${err})`; + } - if (await vscode.window.showInformationMessage(message, dismiss, disable) === disable) { - settings.toggleSetting("configurationWarnings", "enabled", "disabled"); - } + if (await vscode.window.showInformationMessage(message, dismiss, disable) === disable) { + settings.toggleSetting("configurationWarnings", "enabled", "disabled"); } } } return result; } - private async handleRequestCustomConfig(requestFile: string): Promise { - await this.provideCustomConfiguration(vscode.Uri.file(requestFile), requestFile); + private handleRequestCustomConfig(file: string): void { + const uri: vscode.Uri = vscode.Uri.file(file); + const client: Client = clients.getClientFor(uri); + if (client instanceof DefaultClient) { + const defaultClient: DefaultClient = client as DefaultClient; + void defaultClient.provideCustomConfiguration(uri).catch(logAndReturn.undefined); + } } private isExternalHeader(uri: vscode.Uri): boolean { @@ -2386,13 +2350,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, (requestFile: string) => { - const client: Client = clients.getClientFor(vscode.Uri.file(requestFile)); - if (client instanceof DefaultClient) { - const defaultClient: DefaultClient = client as DefaultClient; - void defaultClient.handleRequestCustomConfig(requestFile); - } - }); + this.languageClient.onNotification(RequestCustomConfig, (e) => this.handleRequestCustomConfig(e)); this.languageClient.onNotification(IntelliSenseResultNotification, (e) => this.handleIntelliSenseResult(e)); this.languageClient.onNotification(PublishRefactorDiagnosticsNotification, publishRefactorDiagnostics); RegisterCodeAnalysisNotifications(this.languageClient); @@ -3078,7 +3036,7 @@ export class DefaultClient implements Client { util.isOptionalArrayOfString(input.configuration.forcedInclude); } - private sendCustomConfigurations(configs: any, providerVersion: Version, wasRequested: boolean): void { + private sendCustomConfigurations(configs: any, providerVersion: Version): void { // configs is marked as 'any' because it is untrusted data coming from a 3rd-party. We need to sanitize it before sending it to the language server. if (!configs || !(configs instanceof Array)) { console.warn("discarding invalid SourceFileConfigurationItems[]: " + configs); @@ -3144,9 +3102,10 @@ export class DefaultClient implements Client { workspaceFolderUri: this.RootUri?.toString() }; - if (wasRequested) { - void this.languageClient.sendNotification(CustomConfigurationHighPriorityNotification, params).catch(logAndReturn.undefined); - } + // We send the higher priority notification to ensure we don't deadlock if the request is blocking the queue. + // We send the normal priority notification to avoid a race that could result in a redundant request when racing with + // the reset of custom configurations. + void this.languageClient.sendNotification(CustomConfigurationHighPriorityNotification, params).catch(logAndReturn.undefined); void this.languageClient.sendNotification(CustomConfigurationNotification, params).catch(logAndReturn.undefined); } @@ -4092,7 +4051,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, requestFile?: string, replaceExisting?: boolean): Promise { return Promise.resolve(); } + provideCustomConfiguration(docUri: vscode.Uri): Promise { return Promise.resolve(); } logDiagnostics(): Promise { return Promise.resolve(); } rescanFolder(): Promise { return Promise.resolve(); } toggleReferenceResultsView(): void { } diff --git a/Extension/src/LanguageServer/protocolFilter.ts b/Extension/src/LanguageServer/protocolFilter.ts index 4bc3e45fe..742727241 100644 --- a/Extension/src/LanguageServer/protocolFilter.ts +++ b/Extension/src/LanguageServer/protocolFilter.ts @@ -43,7 +43,6 @@ export function createProtocolFilter(): Middleware { client.sendDidChangeSettings(); document = await vscode.languages.setTextDocumentLanguage(document, "cpp"); } - await client.provideCustomConfiguration(document.uri, undefined); // client.takeOwnership() will call client.TrackedDocuments.add() again, but that's ok. It's a Set. client.onDidOpenTextDocument(document); client.takeOwnership(document); @@ -52,8 +51,7 @@ export function createProtocolFilter(): Middleware { // For a file already open when we activate, sometimes we don't get any notifications about visible // or active text editors, visible ranges, or text selection. As a workaround, we trigger // onDidChangeVisibleTextEditors here, only for the first file opened. - if (!anyFileOpened) - { + if (!anyFileOpened) { anyFileOpened = true; const cppEditors: vscode.TextEditor[] = vscode.window.visibleTextEditors.filter(e => util.isCpp(e.document)); await client.onDidChangeVisibleTextEditors(cppEditors);