From cd74001c3d0fb6dad6fba6062276a9b03d5d2fd0 Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Tue, 6 Jun 2023 22:58:41 +0200 Subject: [PATCH 1/3] Log source modules that provide most completions --- src/lsp-server.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lsp-server.ts b/src/lsp-server.ts index e2183809..c4b4693b 100644 --- a/src/lsp-server.ts +++ b/src/lsp-server.ts @@ -663,6 +663,8 @@ export class LspServer { optionalReplacementRange: optionalReplacementSpan ? Range.fromTextSpan(optionalReplacementSpan) : undefined, }; const completions: lsp.CompletionItem[] = []; + const logLargeCompletionProviders = entries.length > 5000; + const largeCompletionsMap = new Map(); for (const entry of entries || []) { if (entry.kind === 'warning') { continue; @@ -672,6 +674,20 @@ export class LspServer { continue; } completions.push(completion); + if (logLargeCompletionProviders) { + const source = entry.source || '[no module]'; + const count = largeCompletionsMap.get(source) || 0; + largeCompletionsMap.set(source, count + 1); + } + } + if (logLargeCompletionProviders) { + const largeCompletionsList: [string, number][] = []; + for (const [key, count] of largeCompletionsMap.entries()) { + largeCompletionsList.push([key, count]); + } + largeCompletionsList.sort((a, b) => b[1] - a[1]).splice(100); + const table = largeCompletionsList.map(([key, count]) => ` ${key}: ${count}`).join('\n'); + this.logger.warn(`Total completions count: ${entries.length}. Modules contributing most completions:\n${table}`); } return lsp.CompletionList.create(completions, isIncomplete); } From 087c950d47fd64acb5a3d6b7d84668734d6d4249 Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Mon, 16 Oct 2023 23:07:05 +0200 Subject: [PATCH 2/3] tweaks --- src/features/inlay-hints.ts | 6 +++--- src/features/source-definition.ts | 10 ++++----- src/lsp-client.ts | 6 +++--- src/lsp-server.ts | 36 +++++++++++++++++-------------- src/test-utils.ts | 2 +- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/features/inlay-hints.ts b/src/features/inlay-hints.ts index 4a273aa4..d1d3f350 100644 --- a/src/features/inlay-hints.ts +++ b/src/features/inlay-hints.ts @@ -34,21 +34,21 @@ export class TypeScriptInlayHintsProvider { token?: lsp.CancellationToken, ): Promise { if (tspClient.apiVersion.lt(TypeScriptInlayHintsProvider.minVersion)) { - lspClient.showErrorMessage('Inlay Hints request failed. Requires TypeScript 4.4+.'); + lspClient.showWarningMessage('Inlay Hints request failed. Requires TypeScript 4.4+.'); return []; } const file = uriToPath(uri); if (!file) { - lspClient.showErrorMessage('Inlay Hints request failed. No resource provided.'); + lspClient.showWarningMessage('Inlay Hints request failed. No resource provided.'); return []; } const document = documents.get(file); if (!document) { - lspClient.showErrorMessage('Inlay Hints request failed. File not opened in the editor.'); + lspClient.showWarningMessage('Inlay Hints request failed. File not opened in the editor.'); return []; } diff --git a/src/features/source-definition.ts b/src/features/source-definition.ts index 5b492a63..3711e83e 100644 --- a/src/features/source-definition.ts +++ b/src/features/source-definition.ts @@ -32,26 +32,26 @@ export class SourceDefinitionCommand { token?: lsp.CancellationToken, ): Promise { if (tspClient.apiVersion.lt(SourceDefinitionCommand.minVersion)) { - lspClient.showErrorMessage('Go to Source Definition failed. Requires TypeScript 4.7+.'); + lspClient.showWarningMessage('Go to Source Definition failed. Requires TypeScript 4.7+.'); return; } if (!position || typeof position.character !== 'number' || typeof position.line !== 'number') { - lspClient.showErrorMessage('Go to Source Definition failed. Invalid position.'); + lspClient.showWarningMessage('Go to Source Definition failed. Invalid position.'); return; } let file: string | undefined; if (!uri || typeof uri !== 'string' || !(file = uriToPath(uri))) { - lspClient.showErrorMessage('Go to Source Definition failed. No resource provided.'); + lspClient.showWarningMessage('Go to Source Definition failed. No resource provided.'); return; } const document = documents.get(file); if (!document) { - lspClient.showErrorMessage('Go to Source Definition failed. File not opened in the editor.'); + lspClient.showWarningMessage('Go to Source Definition failed. File not opened in the editor.'); return; } @@ -62,7 +62,7 @@ export class SourceDefinitionCommand { }, async () => { const response = await tspClient.request(CommandTypes.FindSourceDefinition, args, token); if (response.type !== 'response' || !response.body) { - lspClient.showErrorMessage('No source definitions found.'); + lspClient.showWarningMessage('No source definitions found.'); return; } return response.body.map(reference => toLocation(reference, documents)); diff --git a/src/lsp-client.ts b/src/lsp-client.ts index bf154a7f..ea2f9076 100644 --- a/src/lsp-client.ts +++ b/src/lsp-client.ts @@ -19,7 +19,7 @@ export interface LspClient { createProgressReporter(token?: lsp.CancellationToken, workDoneProgress?: lsp.WorkDoneProgressReporter): Promise; withProgress(options: WithProgressOptions, task: (progress: lsp.WorkDoneProgressReporter) => Promise): Promise; publishDiagnostics(args: lsp.PublishDiagnosticsParams): void; - showErrorMessage(message: string): void; + showWarningMessage(message: string): void; logMessage(args: lsp.LogMessageParams): void; applyWorkspaceEdit(args: lsp.ApplyWorkspaceEditParams): Promise; rename(args: lsp.TextDocumentPositionParams): Promise; @@ -55,8 +55,8 @@ export class LspClientImpl implements LspClient { this.connection.sendDiagnostics(params); } - showErrorMessage(message: string): void { - this.connection.sendNotification(lsp.ShowMessageNotification.type, { type: MessageType.Error, message }); + showWarningMessage(message: string): void { + this.connection.sendRequest(lsp.ShowMessageRequest.type, { type: MessageType.Warning, message }); } logMessage(args: lsp.LogMessageParams): void { diff --git a/src/lsp-server.ts b/src/lsp-server.ts index c6606485..4f5fba01 100644 --- a/src/lsp-server.ts +++ b/src/lsp-server.ts @@ -668,28 +668,32 @@ export class LspServer { const completions = asCompletionItems(entries, this.completionDataCache, file, params.position, document, this.documents, completionOptions, this.features, completionContext); if (entries.length > 5000) { - const largeCompletionsMap = new Map(); - for (const entry of entries) { - if (!entry.source) { - continue; - } + setImmediate(() => this.triggerSlowCompletionsWarning(entries)); + } - const { source } = entry; - const count = largeCompletionsMap.get(source) || 0; - largeCompletionsMap.set(source, count + 1); - } + return lsp.CompletionList.create(completions, isIncomplete); + } - const largeCompletionsList: [string, number][] = []; - for (const [key, count] of largeCompletionsMap.entries()) { - largeCompletionsList.push([key, count]); + private triggerSlowCompletionsWarning(entries: readonly ts.server.protocol.CompletionEntry[]): void { + const largeCompletionsMap = new Map(); + for (const entry of entries) { + if (!entry.source) { + continue; } - largeCompletionsList.sort((a, b) => b[1] - a[1]).splice(100); - const table = largeCompletionsList.map(([key, count]) => ` ${key}: ${count}`).join('\n'); - this.logger.warn(`Total completions count: ${entries.length}. Modules contributing most completions:\n${table}`); + const { source } = entry; + const count = largeCompletionsMap.get(source) || 0; + largeCompletionsMap.set(source, count + 1); } - return lsp.CompletionList.create(completions, isIncomplete); + const largeCompletionsList: [string, number][] = []; + for (const [key, count] of largeCompletionsMap.entries()) { + largeCompletionsList.push([key, count]); + } + + largeCompletionsList.sort((a, b) => b[1] - a[1]).splice(25); + const table = largeCompletionsList.map(([key, count]) => ` ${key}: ${count}`).join('\n'); + this.options.lspClient.showWarningMessage(`Completion request took a long time (over xxxx ms).\n\nTotal completions count: ${entries.length}.\n\nModules contributing most completions:\n${table}`); } async completionResolve(item: lsp.CompletionItem, token?: lsp.CancellationToken): Promise { diff --git a/src/test-utils.ts b/src/test-utils.ts index 158850e1..9d7b9da2 100644 --- a/src/test-utils.ts +++ b/src/test-utils.ts @@ -143,7 +143,7 @@ export class TestLspClient implements LspClient { return this.options.publishDiagnostics(args); } - showErrorMessage(message: string): void { + showWarningMessage(message: string): void { this.logger.error(`[showErrorMessage] ${message}`); } From c943503feff37aa34474487da75df5193b44bd1f Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Fri, 20 Oct 2023 21:20:42 +0200 Subject: [PATCH 3/3] bump limit --- src/lsp-server.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lsp-server.ts b/src/lsp-server.ts index 4f5fba01..b41ab355 100644 --- a/src/lsp-server.ts +++ b/src/lsp-server.ts @@ -40,6 +40,8 @@ import { Position, Range } from './utils/typeConverters.js'; import { CodeActionKind } from './utils/types.js'; import { ConfigurationManager } from './configuration-manager.js'; +const LARGE_NUMBER_OF_COMPLETIONS_WARNING_TRIGGER = 10000; + export class LspServer { private _tspClient: TspClient | null = null; private hasShutDown = false; @@ -667,14 +669,14 @@ export class LspServer { }; const completions = asCompletionItems(entries, this.completionDataCache, file, params.position, document, this.documents, completionOptions, this.features, completionContext); - if (entries.length > 5000) { - setImmediate(() => this.triggerSlowCompletionsWarning(entries)); + if (entries.length > LARGE_NUMBER_OF_COMPLETIONS_WARNING_TRIGGER) { + setImmediate(() => this.triggerLargeNumberOfCompletionsWarning(entries)); } return lsp.CompletionList.create(completions, isIncomplete); } - private triggerSlowCompletionsWarning(entries: readonly ts.server.protocol.CompletionEntry[]): void { + private triggerLargeNumberOfCompletionsWarning(entries: readonly ts.server.protocol.CompletionEntry[]): void { const largeCompletionsMap = new Map(); for (const entry of entries) { if (!entry.source) { @@ -693,7 +695,7 @@ export class LspServer { largeCompletionsList.sort((a, b) => b[1] - a[1]).splice(25); const table = largeCompletionsList.map(([key, count]) => ` ${key}: ${count}`).join('\n'); - this.options.lspClient.showWarningMessage(`Completion request took a long time (over xxxx ms).\n\nTotal completions count: ${entries.length}.\n\nModules contributing most completions:\n${table}`); + this.options.lspClient.showWarningMessage(`Large number (${entries.length}) of completions received.\n\nModules contributing most completions:\nConsider ignoring the biggest offenders with the \`autoImportFileExcludePatterns\` option. ${table}`); } async completionResolve(item: lsp.CompletionItem, token?: lsp.CancellationToken): Promise {