diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index ad58abcdd72..1806a8beaf5 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -18,6 +18,7 @@ import * as Previewer from '../utils/previewer'; import * as typeConverters from '../utils/typeConverters'; import TypingsStatus from '../utils/typingsStatus'; import FileConfigurationManager from './fileConfigurationManager'; +import { snippetForFunctionCall } from '../utils/snippetForFunctionCall'; const localize = nls.loadMessageBundle(); @@ -624,92 +625,6 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider } } -export function snippetForFunctionCall( - item: { insertText?: string | vscode.SnippetString, label: string }, - displayParts: ReadonlyArray -): vscode.SnippetString { - if (item.insertText && typeof item.insertText !== 'string') { - return item.insertText; - } - - const snippet = new vscode.SnippetString(`${item.insertText || item.label}(`); - - const parameterListParts = getParameterListParts(displayParts, item.label); - appendJoinedPlaceholders(snippet, parameterListParts.parts, ', '); - - if (parameterListParts.hasOptionalParameters) { - snippet.appendTabstop(); - } - snippet.appendText(')'); - snippet.appendTabstop(0); - return snippet; -} - -function appendJoinedPlaceholders( - snippet: vscode.SnippetString, - parts: ReadonlyArray, - joiner: string -) { - for (let i = 0; i < parts.length; ++i) { - const paramterPart = parts[i]; - snippet.appendPlaceholder(paramterPart.text); - if (i !== parts.length - 1) { - snippet.appendText(joiner); - } - } -} - -interface ParamterPart { - readonly text: string; - readonly optional?: boolean; -} - -function getParameterListParts( - displayParts: ReadonlyArray, - label: string, -): { - parts: ReadonlyArray - hasOptionalParameters: boolean - } { - let parts: ParamterPart[] = []; - let isInMethod = false; - let hasOptionalParameters = false; - - let parenCount = 0; - let i = 0; - for (; i < displayParts.length; ++i) { - const part = displayParts[i]; - if ((part.kind === 'methodName' || part.kind === 'functionName' || part.kind === 'text') && part.text === label) { - if (parenCount === 0) { - isInMethod = true; - } - } if (part.kind === 'parameterName' && parenCount === 1 && isInMethod) { - // Only take top level paren names - const next = displayParts[i + 1]; - // Skip optional parameters - const nameIsFollowedByOptionalIndicator = next && next.text === '?'; - if (!nameIsFollowedByOptionalIndicator) { - parts.push(part); - } - hasOptionalParameters = hasOptionalParameters || nameIsFollowedByOptionalIndicator; - } else if (part.kind === 'punctuation') { - if (part.text === '(') { - ++parenCount; - } else if (part.text === ')') { - --parenCount; - if (parenCount <= 0 && isInMethod) { - break; - } - } else if (part.text === '...' && parenCount === 1) { - // Found rest parmeter. Do not fill in any further arguments - hasOptionalParameters = true; - break; - } - } - } - return { hasOptionalParameters, parts }; -} - function shouldExcludeCompletionEntry( element: Proto.CompletionEntry, completionConfiguration: CompletionConfiguration diff --git a/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts b/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts index 257b0890c26..e8dfeb8ba16 100644 --- a/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts +++ b/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; -import { snippetForFunctionCall } from '../features/completions'; +import { snippetForFunctionCall } from "../utils/snippetForFunctionCall"; suite('typescript function call snippets', () => { test('Should use label as function name', async () => { diff --git a/extensions/typescript-language-features/src/utils/snippetForFunctionCall.ts b/extensions/typescript-language-features/src/utils/snippetForFunctionCall.ts new file mode 100644 index 00000000000..ff44cb1fbf8 --- /dev/null +++ b/extensions/typescript-language-features/src/utils/snippetForFunctionCall.ts @@ -0,0 +1,84 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as Proto from '../protocol'; + +export function snippetForFunctionCall( + item: { insertText?: string | vscode.SnippetString; label: string; }, + displayParts: ReadonlyArray +): vscode.SnippetString { + if (item.insertText && typeof item.insertText !== 'string') { + return item.insertText; + } + const snippet = new vscode.SnippetString(`${item.insertText || item.label}(`); + const parameterListParts = getParameterListParts(displayParts, item.label); + appendJoinedPlaceholders(snippet, parameterListParts.parts, ', '); + if (parameterListParts.hasOptionalParameters) { + snippet.appendTabstop(); + } + snippet.appendText(')'); + snippet.appendTabstop(0); + return snippet; +} + +function appendJoinedPlaceholders(snippet: vscode.SnippetString, parts: ReadonlyArray, joiner: string) { + for (let i = 0; i < parts.length; ++i) { + const paramterPart = parts[i]; + snippet.appendPlaceholder(paramterPart.text); + if (i !== parts.length - 1) { + snippet.appendText(joiner); + } + } +} + +interface ParamterPart { + readonly text: string; + readonly optional?: boolean; +} + +function getParameterListParts(displayParts: ReadonlyArray, label: string): { + parts: ReadonlyArray; + hasOptionalParameters: boolean; +} { + let parts: ParamterPart[] = []; + let isInMethod = false; + let hasOptionalParameters = false; + let parenCount = 0; + let i = 0; + for (; i < displayParts.length; ++i) { + const part = displayParts[i]; + if ((part.kind === 'methodName' || part.kind === 'functionName' || part.kind === 'text') && part.text === label) { + if (parenCount === 0) { + isInMethod = true; + } + } + if (part.kind === 'parameterName' && parenCount === 1 && isInMethod) { + // Only take top level paren names + const next = displayParts[i + 1]; + // Skip optional parameters + const nameIsFollowedByOptionalIndicator = next && next.text === '?'; + if (!nameIsFollowedByOptionalIndicator) { + parts.push(part); + } + hasOptionalParameters = hasOptionalParameters || nameIsFollowedByOptionalIndicator; + } + else if (part.kind === 'punctuation') { + if (part.text === '(') { + ++parenCount; + } else if (part.text === ')') { + --parenCount; + if (parenCount <= 0 && isInMethod) { + break; + } + } else if (part.text === '...' && parenCount === 1) { + // Found rest parmeter. Do not fill in any further arguments + hasOptionalParameters = true; + break; + } + } + } + return { hasOptionalParameters, parts }; +}