diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index 594687ab97f..70f61d61a13 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -32,8 +32,16 @@ interface CompletionContext { readonly isNewIdentifierLocation: boolean; readonly isMemberCompletion: boolean; readonly isInValidCommitCharacterContext: boolean; - readonly enableCallCompletions: boolean; + readonly dotAccessorContext?: DotAccessorContext; + + readonly enableCallCompletions: boolean; + readonly useCodeSnippetsOnMethodSuggest: boolean, + + readonly wordRange: vscode.Range | undefined; + readonly line: string; + + readonly useFuzzyWordRangeLogic: boolean, } class MyCompletionItem extends vscode.CompletionItem { @@ -42,10 +50,8 @@ class MyCompletionItem extends vscode.CompletionItem { constructor( public readonly position: vscode.Position, public readonly document: vscode.TextDocument, - line: string, public readonly tsEntry: Proto.CompletionEntry, - useCodeSnippetsOnMethodSuggest: boolean, - public readonly completionContext: CompletionContext, + private readonly completionContext: CompletionContext, public readonly metadata: any | undefined, ) { super(tsEntry.name, MyCompletionItem.convertKind(tsEntry.kind)); @@ -63,13 +69,13 @@ class MyCompletionItem extends vscode.CompletionItem { } this.position = position; - this.useCodeSnippet = useCodeSnippetsOnMethodSuggest && (this.kind === vscode.CompletionItemKind.Function || this.kind === vscode.CompletionItemKind.Method); + this.useCodeSnippet = completionContext.useCodeSnippetsOnMethodSuggest && (this.kind === vscode.CompletionItemKind.Function || this.kind === vscode.CompletionItemKind.Method); if (tsEntry.replacementSpan) { let replaceRange = typeConverters.Range.fromTextSpan(tsEntry.replacementSpan); // Make sure we only replace a single line at most if (!replaceRange.isSingleLine) { - replaceRange = new vscode.Range(replaceRange.start.line, replaceRange.start.character, replaceRange.start.line, line.length); + replaceRange = new vscode.Range(replaceRange.start.line, replaceRange.start.character, replaceRange.start.line, completionContext.line.length); } this.range = { inserting: new vscode.Range(replaceRange.start, position), @@ -78,12 +84,12 @@ class MyCompletionItem extends vscode.CompletionItem { } this.insertText = tsEntry.insertText; - this.filterText = this.getFilterText(line, tsEntry.insertText); + this.filterText = this.getFilterText(completionContext.line, tsEntry.insertText); if (completionContext.isMemberCompletion && completionContext.dotAccessorContext) { this.filterText = completionContext.dotAccessorContext.text + (this.insertText || this.label); if (!this.range) { - const replacementRange = this.getReplaceRange(line); + const replacementRange = this.getFuzzyWordRange(); if (replacementRange) { this.range = { inserting: completionContext.dotAccessorContext.range, @@ -126,13 +132,13 @@ class MyCompletionItem extends vscode.CompletionItem { } } } - this.resolveRange(line); + this.resolveRange(); } private getFilterText(line: string, insertText: string | undefined): string | undefined { // Handle private field completions if (this.tsEntry.name.startsWith('#')) { - const wordRange = this.document.getWordRangeAtPosition(this.position); + const wordRange = this.completionContext.wordRange; const wordStart = wordRange ? line.charAt(wordRange.start.character) : undefined; if (insertText) { if (insertText.startsWith('this.#')) { @@ -164,12 +170,12 @@ class MyCompletionItem extends vscode.CompletionItem { return insertText; } - private resolveRange(line: string): void { + private resolveRange(): void { if (this.range) { return; } - const replaceRange = this.getReplaceRange(line); + const replaceRange = this.getFuzzyWordRange(); if (replaceRange) { this.range = { inserting: new vscode.Range(replaceRange.start, this.position), @@ -178,23 +184,21 @@ class MyCompletionItem extends vscode.CompletionItem { } } - private getReplaceRange(line: string) { - const wordRange = this.document.getWordRangeAtPosition(this.position); - let replaceRange = wordRange; - - // Try getting longer, prefix based range for completions that span words - const text = line.slice(Math.max(0, this.position.character - this.label.length), this.position.character).toLowerCase(); - const entryName = this.label.toLowerCase(); - for (let i = entryName.length; i >= 0; --i) { - if (text.endsWith(entryName.substr(0, i)) && (!wordRange || wordRange.start.character > this.position.character - i)) { - replaceRange = new vscode.Range( - new vscode.Position(this.position.line, Math.max(0, this.position.character - i)), - this.position); - break; + private getFuzzyWordRange() { + if (this.completionContext.useFuzzyWordRangeLogic) { + // Try getting longer, prefix based range for completions that span words + const text = this.completionContext.line.slice(Math.max(0, this.position.character - this.label.length), this.position.character).toLowerCase(); + const entryName = this.label.toLowerCase(); + for (let i = entryName.length; i >= 0; --i) { + if (text.endsWith(entryName.substr(0, i)) && (!this.completionContext.wordRange || this.completionContext.wordRange.start.character > this.position.character - i)) { + return new vscode.Range( + new vscode.Position(this.position.line, Math.max(0, this.position.character - i)), + this.position); + } } } - return replaceRange; + return this.completionContext.wordRange; } private static convertKind(kind: string): vscode.CompletionItemKind { @@ -498,15 +502,20 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider metadata = response.metadata; } + const wordRange = document.getWordRangeAtPosition(position); const isInValidCommitCharacterContext = this.isInValidCommitCharacterContext(document, position); const items = entries .filter(entry => !shouldExcludeCompletionEntry(entry, completionConfiguration)) - .map(entry => new MyCompletionItem(position, document, line.text, entry, completionConfiguration.useCodeSnippetsOnMethodSuggest, { + .map(entry => new MyCompletionItem(position, document, entry, { isNewIdentifierLocation, isMemberCompletion, dotAccessorContext, isInValidCommitCharacterContext, - enableCallCompletions: !completionConfiguration.useCodeSnippetsOnMethodSuggest + enableCallCompletions: !completionConfiguration.useCodeSnippetsOnMethodSuggest, + wordRange, + line: line.text, + useCodeSnippetsOnMethodSuggest: completionConfiguration.useCodeSnippetsOnMethodSuggest, + useFuzzyWordRangeLogic: this.client.apiVersion.lt(API.v390), }, metadata)); return new vscode.CompletionList(items, isIncomplete); }