improve resolved completionItem

Closes #137
This commit is contained in:
Qiming Zhao 2020-04-15 16:15:19 +08:00
parent 17fdfd9462
commit eec50a98f2
6 changed files with 113 additions and 55 deletions

View file

@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken, Command, CompletionContext, CompletionItem, InsertTextFormat, MarkupContent, MarkupKind, Position, TextEdit, CompletionList } from 'vscode-languageserver-protocol'
import { CancellationToken, Command, CompletionContext, Range, CompletionItem, InsertTextFormat, MarkupContent, MarkupKind, Position, TextEdit, CompletionList } from 'vscode-languageserver-protocol'
import { TextDocument } from 'vscode-languageserver-textdocument'
import { commands, workspace } from 'coc.nvim'
import { CompletionItemProvider } from 'coc.nvim/lib/provider'
@ -11,7 +11,7 @@ import * as PConst from '../protocol.const'
import { ITypeScriptServiceClient, ServerResponse } from '../typescriptService'
import API from '../utils/api'
import { applyCodeAction } from '../utils/codeAction'
import { convertCompletionEntry, getParameterListParts } from '../utils/completionItem'
import { DotAccessorContext, convertCompletionEntry, getParameterListParts } from '../utils/completionItem'
import * as Previewer from '../utils/previewer'
import * as typeConverters from '../utils/typeConverters'
import TypingsStatus from '../utils/typingsStatus'
@ -111,8 +111,6 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
}
const { completeOption } = this
const doc = workspace.getDocument(uri)
const args: Proto.CompletionsRequestArgs & { includeAutomaticOptionalChainCompletions?: boolean } = {
...typeConverters.Position.toFileLocationRequestArgs(file, position),
includeExternalModuleExports: completeOption.autoImports,
@ -121,9 +119,14 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
includeAutomaticOptionalChainCompletions: completeOption.includeAutomaticOptionalChainCompletions
}
let msg: ReadonlyArray<Proto.CompletionEntry> | undefined
let entries: ReadonlyArray<Proto.CompletionEntry> | undefined
let dotAccessorContext: DotAccessorContext | undefined
let isNewIdentifierLocation = true
let isMemberCompletion = false
let isIncomplete = false
const isInValidCommitCharacterContext = this.isInValidCommitCharacterContext(document, position)
if (this.client.apiVersion.gte(API.v300)) {
try {
const response = await this.client.interruptGetErr(() => this.client.execute('completionInfo', args, token))
@ -131,7 +134,20 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
return null
}
isNewIdentifierLocation = response.body.isNewIdentifierLocation
msg = response.body.entries
isMemberCompletion = response.body.isMemberCompletion
if (isMemberCompletion) {
const dotMatch = preText.slice(0, position.character).match(/\??\.\s*$/) || undefined
if (dotMatch) {
const range = Range.create({
line: position.line,
character: position.character - dotMatch.length
}, position)
const text = document.getText(range)
dotAccessorContext = { range, text }
}
}
isIncomplete = (response as any).metadata && (response as any).metadata.isIncomplete
entries = response.body.entries
} catch (e) {
if (e.message == 'No content available.') {
return null
@ -143,11 +159,11 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
if (response.type !== 'response' || !response.body) {
return null
}
msg = response.body
entries = response.body
}
const completionItems: CompletionItem[] = []
for (const element of msg) {
for (const element of entries) {
if (shouldExcludeCompletionEntry(element, completeOption)) {
continue
}
@ -155,21 +171,17 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
element,
uri,
position,
completeOption.completeFunctionCalls,
isNewIdentifierLocation
{
isNewIdentifierLocation,
isMemberCompletion,
enableCallCompletions: completeOption.completeFunctionCalls,
isInValidCommitCharacterContext,
dotAccessorContext,
}
)
completionItems.push(item)
}
let startcol: number | null = null
if (triggerCharacter == '@' && !doc.isWord('@')) {
startcol = option.col - 1
}
let res: any = {
startcol,
isIncomplete: false,
items: completionItems
}
return res
return { isIncomplete, items: completionItems }
}
private getTsTriggerCharacter(context: CompletionContext): Proto.CompletionsTriggerCharacter | undefined {
@ -229,10 +241,9 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
return item
}
const detail = details[0]
item.detail = detail.displayParts.length
? Previewer.plain(detail.displayParts)
: undefined
if (!item.detail && detail.displayParts.length) {
item.detail = Previewer.plain(detail.displayParts)
}
item.documentation = this.getDocumentation(detail)
const { command, additionalTextEdits } = this.getCodeActions(detail, filepath)
if (command) item.command = command
@ -243,6 +254,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
this.createSnippetOfFunctionCall(item, detail)
}
}
return item
}
@ -390,6 +402,24 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
return true
}
}
private isInValidCommitCharacterContext(
document: TextDocument,
position: Position
): boolean {
if (this.client.apiVersion.lt(API.v320)) {
// Workaround for https://github.com/Microsoft/TypeScript/issues/27742
// Only enable dot completions when previous character not a dot preceded by whitespace.
// Prevents incorrectly completing while typing spread operators.
if (position.character > 1) {
const preText = document.getText(Range.create(
position.line, 0,
position.line, position.character))
return preText.match(/(\s|^)\.$/ig) === null
}
}
return true
}
}
function shouldExcludeCompletionEntry(

View file

@ -20,7 +20,7 @@ const getSymbolKind = (kind: string): SymbolKind => {
return SymbolKind.Enum
case PConst.Kind.interface:
return SymbolKind.Interface
case PConst.Kind.memberFunction:
case PConst.Kind.method:
return SymbolKind.Method
case PConst.Kind.memberVariable:
return SymbolKind.Property

View file

@ -87,7 +87,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip
return super.getSymbolRange(document, item)
case PConst.Kind.class:
case PConst.Kind.memberFunction:
case PConst.Kind.method:
case PConst.Kind.memberVariable:
case PConst.Kind.memberGetAccessor:
case PConst.Kind.memberSetAccessor:

View file

@ -87,7 +87,7 @@ export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBase
}
// fallthrough
case PConst.Kind.memberFunction:
case PConst.Kind.method:
case PConst.Kind.memberVariable:
case PConst.Kind.memberGetAccessor:
case PConst.Kind.memberSetAccessor:

View file

@ -12,6 +12,7 @@ export class Kind {
public static readonly constructSignature = 'construct'
public static readonly directory = 'directory'
public static readonly enum = 'enum'
public static readonly enumMember = 'enum member'
public static readonly externalModuleName = 'external module name'
public static readonly function = 'function'
public static readonly indexSignature = 'index'
@ -20,7 +21,7 @@ export class Kind {
public static readonly let = 'let'
public static readonly localFunction = 'local function'
public static readonly localVariable = 'local var'
public static readonly memberFunction = 'method'
public static readonly method = 'method'
public static readonly memberGetAccessor = 'getter'
public static readonly memberSetAccessor = 'setter'
public static readonly memberVariable = 'property'
@ -32,6 +33,7 @@ export class Kind {
public static readonly warning = 'warning'
public static readonly string = 'string'
public static readonly parameter = 'parameter'
public static readonly typeParameter = 'type parameter'
}
export class DiagnosticCategory {

View file

@ -2,56 +2,57 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { workspace } from 'coc.nvim'
import { Range, CompletionItem, CompletionItemKind, InsertTextFormat, Position, TextEdit } from 'vscode-languageserver-protocol'
import * as Proto from '../protocol'
import * as PConst from '../protocol.const'
interface CommitCharactersSettings {
readonly isNewIdentifierLocation: boolean
readonly isInValidCommitCharacterContext: boolean
readonly useCodeSnippetsOnMethodSuggest: boolean
}
interface ParamterListParts {
readonly parts: ReadonlyArray<Proto.SymbolDisplayPart>
readonly hasOptionalParameters: boolean
}
export interface DotAccessorContext {
readonly range: Range
readonly text: string
}
export interface CompletionContext {
readonly isNewIdentifierLocation: boolean
readonly isMemberCompletion: boolean
readonly isInValidCommitCharacterContext: boolean
readonly enableCallCompletions: boolean
readonly dotAccessorContext?: DotAccessorContext
}
export function convertCompletionEntry(
tsEntry: Proto.CompletionEntry,
uri: string,
position: Position,
useCodeSnippetsOnMethodSuggest: boolean,
isNewIdentifierLocation: boolean
context: CompletionContext,
): CompletionItem {
let label = tsEntry.name
let sortText = tsEntry.sortText
let preselect = false
let detail: string
if (tsEntry.isRecommended) {
// Make sure isRecommended property always comes first
// https://github.com/Microsoft/vscode/issues/40325
sortText = '\0' + sortText
} else if (tsEntry.source) {
// De-prioritze auto-imports
// https://github.com/Microsoft/vscode/issues/40311
preselect = true
}
if (tsEntry.source) {
// De-prioritze auto-imports https://github.com/Microsoft/vscode/issues/40311
sortText = '\uffff' + sortText
} else {
sortText = tsEntry.sortText
}
let kind = convertKind(tsEntry.kind)
let insertTextFormat = (
useCodeSnippetsOnMethodSuggest &&
context.enableCallCompletions &&
(kind === CompletionItemKind.Function ||
kind === CompletionItemKind.Method)
) ? InsertTextFormat.Snippet : InsertTextFormat.PlainText
let insertText = tsEntry.insertText
let document = workspace.getDocument(uri)
let preText = document.getline(position.line).slice(0, position.character)
const isInValidCommitCharacterContext = preText.match(/(^|[a-z_$\(\)\[\]\{\}]|[^.]\.)\s*$/ig) !== null
let commitCharacters = getCommitCharacters(tsEntry, context)
let commitCharacters = getCommitCharacters(tsEntry, { isNewIdentifierLocation, isInValidCommitCharacterContext, useCodeSnippetsOnMethodSuggest })
let optional = tsEntry.kindModifiers && tsEntry.kindModifiers.match(/\boptional\b/)
let textEdit: TextEdit | null = null
if (tsEntry.replacementSpan) {
let { start, end } = tsEntry.replacementSpan
@ -62,17 +63,41 @@ export function convertCompletionEntry(
}
}
}
if (tsEntry.kindModifiers) {
const kindModifiers = new Set(tsEntry.kindModifiers.split(/,|\s+/g))
if (kindModifiers.has(PConst.KindModifiers.optional)) {
label += '?'
}
if (kindModifiers.has(PConst.KindModifiers.color)) {
kind = CompletionItemKind.Color
}
if (tsEntry.kind === PConst.Kind.script) {
for (const extModifier of PConst.KindModifiers.fileExtensionKindModifiers) {
if (kindModifiers.has(extModifier)) {
if (tsEntry.name.toLowerCase().endsWith(extModifier)) {
detail = tsEntry.name
} else {
detail = tsEntry.name + extModifier
}
break
}
}
}
}
return {
label,
insertText,
textEdit,
kind,
preselect,
insertTextFormat,
sortText,
commitCharacters,
detail,
data: {
uri,
optional,
position,
source: tsEntry.source || ''
}
@ -97,7 +122,7 @@ function convertKind(kind: string): CompletionItemKind {
return CompletionItemKind.Field
case PConst.Kind.function:
return CompletionItemKind.Function
case PConst.Kind.memberFunction:
case PConst.Kind.method:
case PConst.Kind.constructSignature:
case PConst.Kind.callSignature:
case PConst.Kind.indexSignature:
@ -121,8 +146,8 @@ function convertKind(kind: string): CompletionItemKind {
return CompletionItemKind.Variable
}
function getCommitCharacters(tsEntry: Proto.CompletionEntry, settings: CommitCharactersSettings): string[] | undefined {
if (settings.isNewIdentifierLocation || !settings.isInValidCommitCharacterContext) {
function getCommitCharacters(tsEntry: Proto.CompletionEntry, context: CompletionContext): string[] | undefined {
if (context.isNewIdentifierLocation || !context.isInValidCommitCharacterContext) {
return undefined
}
const commitCharacters: string[] = []
@ -145,10 +170,11 @@ function getCommitCharacters(tsEntry: Proto.CompletionEntry, settings: CommitCha
case PConst.Kind.memberVariable:
case PConst.Kind.class:
case PConst.Kind.function:
case PConst.Kind.memberFunction:
case PConst.Kind.method:
case PConst.Kind.keyword:
case PConst.Kind.parameter:
commitCharacters.push('.', ',', ';')
if (settings.useCodeSnippetsOnMethodSuggest) {
if (context.enableCallCompletions) {
commitCharacters.push('(')
}
break