From 8febbe9418696bd57c798d653a84084a3e0d099a Mon Sep 17 00:00:00 2001 From: chemzqm Date: Wed, 20 Feb 2019 02:39:22 +0800 Subject: [PATCH] fix snippet generate --- src/server/features/completionItemProvider.ts | 41 +++++----- src/server/utils/SnippetString.ts | 79 +++++++++++++++++++ src/server/utils/completionItem.ts | 3 +- 3 files changed, 103 insertions(+), 20 deletions(-) create mode 100644 src/server/utils/SnippetString.ts diff --git a/src/server/features/completionItemProvider.ts b/src/server/features/completionItemProvider.ts index 3ed4d81..f9f7085 100644 --- a/src/server/features/completionItemProvider.ts +++ b/src/server/features/completionItemProvider.ts @@ -15,6 +15,7 @@ import * as Previewer from '../utils/previewer' import * as typeConverters from '../utils/typeConverters' import TypingsStatus from '../utils/typingsStatus' import FileConfigurationManager, { SuggestOptions } from './fileConfigurationManager' +import SnippetString from '../utils/SnippetString' // command center export interface CommandItem { @@ -352,26 +353,16 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP detail: Proto.CompletionEntryDetails ): void { let { displayParts } = detail - let snippet = (item.insertText || item.label) + '(' // tslint:disable-line const parameterListParts = getParameterListParts(displayParts) - let { parts, hasOptionalParameters } = parameterListParts - let idx = 1 - if (parts.length == 0 && hasOptionalParameters) { - item.insertText = snippet + '${1})$0' - return + const snippet = new SnippetString() + snippet.appendText(`${item.insertText || item.label}(`) + appendJoinedPlaceholders(snippet, parameterListParts.parts, ', ') + if (parameterListParts.hasOptionalParameters) { + snippet.appendTabstop() } - for (let part of parts) { - snippet += '${' + idx + ':' + part.text + '}' // tslint:disable-line - if (idx == parts.length) { - if (hasOptionalParameters) snippet += '${' + (idx + 1) + '}' // tslint:disable-line - } else { - snippet += ', ' - } - idx = idx + 1 - } - snippet += ')$0' - // tslint:disable-next-line:deprecation - item.insertText = snippet + snippet.appendText(')') + snippet.appendTabstop(0) + item.insertText = snippet.value } private async isValidFunctionCompletionContext( @@ -415,3 +406,17 @@ function shouldExcludeCompletionEntry( || (!completionConfiguration.autoImports && element.hasAction) ) } + +function appendJoinedPlaceholders( + snippet: 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) + } + } +} diff --git a/src/server/utils/SnippetString.ts b/src/server/utils/SnippetString.ts new file mode 100644 index 0000000..39cfe43 --- /dev/null +++ b/src/server/utils/SnippetString.ts @@ -0,0 +1,79 @@ +export default class SnippetString { + + static isSnippetString(thing: any): thing is SnippetString { + if (thing instanceof SnippetString) { + return true + } + if (!thing) { + return false + } + return typeof (thing).value === 'string' + } + + private static _escape(value: string): string { + return value.replace(/\$|}|\\/g, '\\$&') + } + + private _tabstop: number = 1 + + value: string + + constructor(value?: string) { + this.value = value || '' + } + + appendText(string: string): SnippetString { + this.value += SnippetString._escape(string) + return this + } + + appendTabstop(number: number = this._tabstop++): SnippetString { + this.value += '$' + this.value += number + return this + } + + appendPlaceholder(value: string | ((snippet: SnippetString) => any), number: number = this._tabstop++): SnippetString { + + if (typeof value === 'function') { + const nested = new SnippetString() + nested._tabstop = this._tabstop + value(nested) + this._tabstop = nested._tabstop + value = nested.value + } else { + value = SnippetString._escape(value) + } + + this.value += '${' + this.value += number + this.value += ':' + this.value += value + this.value += '}' + + return this + } + + appendVariable(name: string, defaultValue?: string | ((snippet: SnippetString) => any)): SnippetString { + + if (typeof defaultValue === 'function') { + const nested = new SnippetString() + nested._tabstop = this._tabstop + defaultValue(nested) + this._tabstop = nested._tabstop + defaultValue = nested.value + + } else if (typeof defaultValue === 'string') { + defaultValue = defaultValue.replace(/\$|}/g, '\\$&') + } + + this.value += '${' + this.value += name + if (defaultValue) { + this.value += ':' + this.value += defaultValue + } + this.value += '}' + return this + } +} diff --git a/src/server/utils/completionItem.ts b/src/server/utils/completionItem.ts index 9a09f62..eaca77e 100644 --- a/src/server/utils/completionItem.ts +++ b/src/server/utils/completionItem.ts @@ -178,7 +178,7 @@ export function getParameterListParts( break case PConst.DisplayPartKind.parameterName: - if (parenCount === 1 && isInMethod) { + if (parenCount === 1 && braceCount === 0 && isInMethod) { // Only take top level paren names const next = displayParts[i + 1] // Skip optional parameters @@ -210,6 +210,5 @@ export function getParameterListParts( break } } - return { hasOptionalParameters, parts } }