Skip to content

Commit

Permalink
fix(completions): don't create snippet kind without `completeFunction…
Browse files Browse the repository at this point in the history
…Calls` (#595)
  • Loading branch information
rchl committed Sep 28, 2022
1 parent 1ed4e2e commit 7f69c27
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 80 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ Some of the preferences can be controlled through the `workspace/didChangeConfig
[language].inlayHints.includeInlayVariableTypeHintsWhenTypeMatchesName: boolean;
/**
* Complete functions with their parameter signature.
*
* This functionality relies on LSP client resolving the completion using the `completionItem/resolve` call. If the
* client can't do that before inserting the completion then it's not safe to enable it as it will result in some
* completions having a snippet type without actually being snippets, which can then cause problems when inserting them.
*
* @default false
*/
completions.completeFunctionCalls: boolean;
Expand Down
15 changes: 11 additions & 4 deletions src/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface ParameterListParts {
readonly hasOptionalParameters: boolean;
}

export function asCompletionItem(entry: tsp.CompletionEntry, optionalReplacementSpan: tsp.TextSpan | undefined, file: string, position: lsp.Position, document: LspDocument, features: SupportedFeatures): lsp.CompletionItem | null {
export function asCompletionItem(entry: tsp.CompletionEntry, optionalReplacementSpan: tsp.TextSpan | undefined, file: string, position: lsp.Position, document: LspDocument, options: WorkspaceConfigurationCompletionOptions, features: SupportedFeatures): lsp.CompletionItem | null {
const item: lsp.CompletionItem = {
label: entry.name,
...features.completionLabelDetails ? { labelDetails: entry.labelDetails } : {},
Expand Down Expand Up @@ -54,7 +54,7 @@ export function asCompletionItem(entry: tsp.CompletionEntry, optionalReplacement
if (isSnippet && !features.completionSnippets) {
return null;
}
if (features.completionSnippets && (isSnippet || item.kind === lsp.CompletionItemKind.Function || item.kind === lsp.CompletionItemKind.Method)) {
if (features.completionSnippets && (isSnippet || canCreateSnippetOfFunctionCall(item.kind, options))) {
// Import statements, Functions and Methods can result in a snippet completion when resolved.
item.insertTextFormat = lsp.InsertTextFormat.Snippet;
}
Expand Down Expand Up @@ -217,7 +217,7 @@ export async function asResolvedCompletionItem(
item.additionalTextEdits = asAdditionalTextEdits(details.codeActions, filepath);
item.command = asCommand(details.codeActions, item.data.file);
}
if (features.completionSnippets && options.completeFunctionCalls && (item.kind === lsp.CompletionItemKind.Function || item.kind === lsp.CompletionItemKind.Method)) {
if (features.completionSnippets && canCreateSnippetOfFunctionCall(item.kind, options)) {
const { line, offset } = item.data;
const position = Position.fromLocation({ line, offset });
const shouldCompleteFunction = await isValidFunctionCompletionContext(filepath, position, client);
Expand All @@ -229,7 +229,7 @@ export async function asResolvedCompletionItem(
return item;
}

export async function isValidFunctionCompletionContext(filepath: string, position: lsp.Position, client: TspClient): Promise<boolean> {
async function isValidFunctionCompletionContext(filepath: string, position: lsp.Position, client: TspClient): Promise<boolean> {
// Workaround for https://github.com/Microsoft/TypeScript/issues/12677
// Don't complete function calls inside of destructive assigments or imports
try {
Expand All @@ -254,6 +254,10 @@ export async function isValidFunctionCompletionContext(filepath: string, positio
}
}

function canCreateSnippetOfFunctionCall(kind: lsp.CompletionItemKind | undefined, options: WorkspaceConfigurationCompletionOptions): boolean {
return options.completeFunctionCalls === true && (kind === lsp.CompletionItemKind.Function || kind === lsp.CompletionItemKind.Method);
}

function createSnippetOfFunctionCall(item: lsp.CompletionItem, detail: tsp.CompletionEntryDetails): void {
const { displayParts } = detail;
const parameterListParts = getParameterListParts(displayParts);
Expand All @@ -267,6 +271,9 @@ function createSnippetOfFunctionCall(item: lsp.CompletionItem, detail: tsp.Compl
snippet.appendTabstop(0);
item.insertText = snippet.value;
item.insertTextFormat = lsp.InsertTextFormat.Snippet;
if (item.textEdit) {
item.textEdit.newText = snippet.value;
}
}

function getParameterListParts(displayParts: ReadonlyArray<tsp.SymbolDisplayPart>): ParameterListParts {
Expand Down
2 changes: 1 addition & 1 deletion src/configuration-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class ConfigurationManager {
}

public setWorkspaceConfiguration(configuration: WorkspaceConfiguration): void {
this.workspaceConfiguration = configuration;
this.workspaceConfiguration = deepmerge({}, configuration);
}

public async setAndConfigureTspClient(workspaceFolder: string | undefined, client: TspClient, hostInfo?: TypeScriptInitializationOptions['hostInfo']): Promise<void> {
Expand Down

0 comments on commit 7f69c27

Please sign in to comment.