From b46f98078dc690e5a7290dc84a6d3fb1d2d5eaf3 Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Fri, 12 Sep 2025 20:20:55 +0200 Subject: [PATCH] fix: align completions code with upstream --- src/completion.ts | 76 +++++++++++++++++++++++++--------------------- src/lsp-server.ts | 17 +++++++++-- src/ts-protocol.ts | 1 + 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/src/completion.ts b/src/completion.ts index e3cd9ad7..e990ad46 100644 --- a/src/completion.ts +++ b/src/completion.ts @@ -24,7 +24,9 @@ interface ParameterListParts { } export interface CompletionContext { + readonly enableCallCompletions: boolean; readonly isMemberCompletion: boolean; + readonly isNewIdentifierLocation: boolean; readonly dotAccessorContext?: { range: lsp.Range; text: string; @@ -63,10 +65,11 @@ export function asCompletionItems( options: WorkspaceConfigurationCompletionOptions, features: SupportedFeatures, completionContext: CompletionContext, + defaultCommitCharacters: readonly string[] | undefined, ): lsp.CompletionItem[] { const completions: lsp.CompletionItem[] = []; for (const entry of entries) { - const completion = asCompletionItem(entry, completionDataCache, file, position, document, filePathConverter, options, features, completionContext); + const completion = asCompletionItem(entry, completionDataCache, file, position, document, filePathConverter, options, features, completionContext, defaultCommitCharacters); if (!completion) { continue; } @@ -85,6 +88,7 @@ function asCompletionItem( options: WorkspaceConfigurationCompletionOptions, features: SupportedFeatures, completionContext: CompletionContext, + defaultCommitCharacters: readonly string[] | undefined, ): lsp.CompletionItem | null { const cacheId = completionDataCache.add({ file, @@ -100,7 +104,7 @@ function asCompletionItem( }); const item: lsp.CompletionItem = { - label: entry.name, + label: entry.name || (entry.insertText ?? ''), kind: asCompletionItemKind(entry.kind), sortText: entry.sortText, preselect: entry.isRecommended, @@ -109,20 +113,20 @@ function asCompletionItem( }, }; + if (entry.source && entry.hasAction) { + // De-prioritze auto-imports + // https://github.com/Microsoft/vscode/issues/40311 + item.sortText = `\uffff${entry.sortText}`; + } + if (features.completionCommitCharactersSupport) { - item.commitCharacters = asCommitCharacters(entry.kind); + item.commitCharacters = asCommitCharacters(completionContext, entry, defaultCommitCharacters); } if (features.completionLabelDetails) { item.labelDetails = entry.labelDetails; } - if (entry.source && entry.hasAction) { - // De-prioritze auto-imports - // https://github.com/Microsoft/vscode/issues/40311 - item.sortText = `\uffff${entry.sortText}`; - } - const { isSnippet, replacementSpan, sourceDisplay } = entry; if (isSnippet && !features.completionSnippets) { return null; @@ -176,6 +180,10 @@ function asCompletionItem( item.tags = [lsp.CompletionItemTag.Deprecated]; } + if (kindModifiers.has(KindModifiers.color)) { + item.kind = lsp.CompletionItemKind.Color; + } + if (entry.kind === ScriptElementKind.scriptElement as ScriptElementKind) { for (const extModifier of KindModifiers.fileExtensionKindModifiers) { if (kindModifiers.has(extModifier)) { @@ -313,35 +321,33 @@ function asCompletionItemKind(kind: ScriptElementKind): lsp.CompletionItemKind { return lsp.CompletionItemKind.Property; } -function asCommitCharacters(kind: ScriptElementKind): string[] | undefined { - const commitCharacters: string[] = []; - switch (kind) { - case ScriptElementKind.memberGetAccessorElement: - case ScriptElementKind.memberSetAccessorElement: - case ScriptElementKind.constructSignatureElement: - case ScriptElementKind.callSignatureElement: - case ScriptElementKind.indexSignatureElement: - case ScriptElementKind.enumElement: - case ScriptElementKind.interfaceElement: - commitCharacters.push('.'); - break; - - case ScriptElementKind.moduleElement: - case ScriptElementKind.alias: - case ScriptElementKind.constElement: - case ScriptElementKind.letElement: - case ScriptElementKind.variableElement: - case ScriptElementKind.localVariableElement: - case ScriptElementKind.memberVariableElement: - case ScriptElementKind.classElement: - case ScriptElementKind.functionElement: - case ScriptElementKind.memberFunctionElement: - commitCharacters.push('.', ','); +function asCommitCharacters( + context: CompletionContext, + entry: ts.server.protocol.CompletionEntry, + defaultCommitCharacters: readonly string[] | undefined, +): string[] | undefined { + const kind = entry.kind as ScriptElementKind; + let commitCharacters = entry.commitCharacters ?? (defaultCommitCharacters ? Array.from(defaultCommitCharacters) : undefined); + if (commitCharacters) { + if (context.enableCallCompletions + && !context.isNewIdentifierLocation + && kind !== ScriptElementKind.warning + && kind !== ScriptElementKind.string) { commitCharacters.push('('); - break; + } + return commitCharacters; + } + + if (kind === ScriptElementKind.warning || kind === ScriptElementKind.string) { // Ambient JS word based suggestion, strings + return undefined; + } + + commitCharacters = ['.', ',', ';']; + if (context.enableCallCompletions) { + commitCharacters.push('('); } - return commitCharacters.length === 0 ? undefined : commitCharacters; + return commitCharacters; } export async function asResolvedCompletionItem( diff --git a/src/lsp-server.ts b/src/lsp-server.ts index 11047e56..f044ab55 100644 --- a/src/lsp-server.ts +++ b/src/lsp-server.ts @@ -531,7 +531,8 @@ export class LspServer { return lsp.CompletionList.create(); } - const { entries, isIncomplete, optionalReplacementSpan, isMemberCompletion } = result; + const { entries, isIncomplete, optionalReplacementSpan, isMemberCompletion, isNewIdentifierLocation } = result; + const defaultCommitCharacters = Object.freeze(result.defaultCommitCharacters); const line = document.getLine(params.position.line); let dotAccessorContext: CompletionContext['dotAccessorContext']; if (isMemberCompletion) { @@ -544,12 +545,24 @@ export class LspServer { } } const completionContext: CompletionContext = { + enableCallCompletions: !completionOptions.completeFunctionCalls, isMemberCompletion, + isNewIdentifierLocation, dotAccessorContext, line, optionalReplacementRange: optionalReplacementSpan ? Range.fromTextSpan(optionalReplacementSpan) : undefined, }; - const completions = asCompletionItems(entries, this.completionDataCache, filepath, params.position, document, this.tsClient, completionOptions, this.features, completionContext); + const completions = asCompletionItems( + entries, + this.completionDataCache, + filepath, + params.position, + document, + this.tsClient, + completionOptions, + this.features, + completionContext, + defaultCommitCharacters); return lsp.CompletionList.create(completions, isIncomplete); } diff --git a/src/ts-protocol.ts b/src/ts-protocol.ts index 696b03b3..f15b71c8 100644 --- a/src/ts-protocol.ts +++ b/src/ts-protocol.ts @@ -293,6 +293,7 @@ export const enum EventName { export class KindModifiers { public static readonly optional = 'optional'; public static readonly deprecated = 'deprecated'; + public static readonly color = 'color'; public static readonly dtsFile = '.d.ts'; public static readonly tsFile = '.ts'; public static readonly tsxFile = '.tsx';