parent
7b363e237e
commit
de68137850
6 changed files with 303 additions and 1 deletions
106
package.json
106
package.json
|
@ -730,7 +730,111 @@
|
||||||
"javascript.suggest.includeAutomaticOptionalChainCompletions": {
|
"javascript.suggest.includeAutomaticOptionalChainCompletions": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true,
|
"default": true,
|
||||||
"description": "%configuration.suggest.includeAutomaticOptionalChainCompletions%",
|
"description": "Enable/disable showing completions on potentially undefined values that insert an optional chain call. Requires TS 3.7+ and strict null checks to be enabled.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"typescript.inlayHints.parameterNames.enabled": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"literals",
|
||||||
|
"all"
|
||||||
|
],
|
||||||
|
"enumDescriptions": [
|
||||||
|
"Disable parameter name hints.",
|
||||||
|
"Enable parameter name hints only for literal arguments.",
|
||||||
|
"Enable parameter name hints for literal and non-literal arguments."
|
||||||
|
],
|
||||||
|
"default": "none",
|
||||||
|
"description": "Enable/disable inlay hints of parameter names.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"typescript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Suppress parameter name hints on arguments whose text is identical to the parameter name.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"typescript.inlayHints.parameterTypes.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable/disable inlay hints of parameter types.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"typescript.inlayHints.variableTypes.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable/disable inlay hints of variable types.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"typescript.inlayHints.propertyDeclarationTypes.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable/disable inlay hints of property declarations.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"typescript.inlayHints.functionLikeReturnTypes.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable/disable inlay hints of return type for function signatures.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"typescript.inlayHints.enumMemberValues.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable/disable inlay hints of enum member values.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"javascript.inlayHints.parameterNames.enabled": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"none",
|
||||||
|
"literals",
|
||||||
|
"all"
|
||||||
|
],
|
||||||
|
"enumDescriptions": [
|
||||||
|
"Disable parameter name hints.",
|
||||||
|
"Enable parameter name hints only for literal arguments.",
|
||||||
|
"Enable parameter name hints for literal and non-literal arguments."
|
||||||
|
],
|
||||||
|
"default": "none",
|
||||||
|
"description": "Enable/disable inlay hints of parameter names.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"javascript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Suppress parameter name hints on arguments whose text is identical to the parameter name.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"javascript.inlayHints.parameterTypes.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable/disable inlay hints of parameter types.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"javascript.inlayHints.variableTypes.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable/disable inlay hints of variable types.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"javascript.inlayHints.propertyDeclarationTypes.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable/disable inlay hints of property declarations.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"javascript.inlayHints.functionLikeReturnTypes.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable/disable inlay hints of return type for function signatures.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
|
"javascript.inlayHints.enumMemberValues.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable/disable inlay hints of enum member values.",
|
||||||
"scope": "resource"
|
"scope": "resource"
|
||||||
},
|
},
|
||||||
"javascript.autoClosingTags": {
|
"javascript.autoClosingTags": {
|
||||||
|
|
|
@ -193,6 +193,7 @@ export default class FileConfigurationManager {
|
||||||
includeCompletionsWithSnippetText: suggestConfig.includeCompletionsWithSnippetText,
|
includeCompletionsWithSnippetText: suggestConfig.includeCompletionsWithSnippetText,
|
||||||
allowIncompleteCompletions: true,
|
allowIncompleteCompletions: true,
|
||||||
displayPartsForJSDoc: true,
|
displayPartsForJSDoc: true,
|
||||||
|
...getInlayHintsPreferences(language),
|
||||||
}
|
}
|
||||||
return preferences
|
return preferences
|
||||||
}
|
}
|
||||||
|
@ -240,3 +241,34 @@ function getJsxAttributeCompletionStyle(config: WorkspaceConfiguration) {
|
||||||
default: return 'auto'
|
default: return 'auto'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class InlayHintSettingNames {
|
||||||
|
static readonly parameterNamesSuppressWhenArgumentMatchesName = 'inlayHints.parameterNames.suppressWhenArgumentMatchesName'
|
||||||
|
static readonly parameterNamesEnabled = 'inlayHints.parameterTypes.enabled'
|
||||||
|
static readonly variableTypesEnabled = 'inlayHints.variableTypes.enabled'
|
||||||
|
static readonly propertyDeclarationTypesEnabled = 'inlayHints.propertyDeclarationTypes.enabled'
|
||||||
|
static readonly functionLikeReturnTypesEnabled = 'inlayHints.functionLikeReturnTypes.enabled'
|
||||||
|
static readonly enumMemberValuesEnabled = 'inlayHints.enumMemberValues.enabled'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInlayHintsPreferences(language: string) {
|
||||||
|
const config = workspace.getConfiguration(language)
|
||||||
|
return {
|
||||||
|
includeInlayParameterNameHints: getInlayParameterNameHintsPreference(config),
|
||||||
|
includeInlayParameterNameHintsWhenArgumentMatchesName: !config.get<boolean>(InlayHintSettingNames.parameterNamesSuppressWhenArgumentMatchesName, true),
|
||||||
|
includeInlayFunctionParameterTypeHints: config.get<boolean>(InlayHintSettingNames.parameterNamesEnabled, false),
|
||||||
|
includeInlayVariableTypeHints: config.get<boolean>(InlayHintSettingNames.variableTypesEnabled, false),
|
||||||
|
includeInlayPropertyDeclarationTypeHints: config.get<boolean>(InlayHintSettingNames.propertyDeclarationTypesEnabled, false),
|
||||||
|
includeInlayFunctionLikeReturnTypeHints: config.get<boolean>(InlayHintSettingNames.functionLikeReturnTypesEnabled, false),
|
||||||
|
includeInlayEnumMemberValueHints: config.get<boolean>(InlayHintSettingNames.enumMemberValuesEnabled, false),
|
||||||
|
} as const
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInlayParameterNameHintsPreference(config: WorkspaceConfiguration) {
|
||||||
|
switch (config.get<string>('inlayHints.parameterNames.enabled')) {
|
||||||
|
case 'none': return 'none'
|
||||||
|
case 'literals': return 'literals'
|
||||||
|
case 'all': return 'all'
|
||||||
|
default: return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
160
src/server/features/inlayHints.ts
Normal file
160
src/server/features/inlayHints.ts
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { CancellationToken, CancellationTokenSource, Disposable, disposeAll, Document, events, Position, Range, TextDocument, workspace } from 'coc.nvim'
|
||||||
|
import type * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import API from '../utils/api'
|
||||||
|
import FileConfigurationManager, { getInlayHintsPreferences } from './fileConfigurationManager'
|
||||||
|
|
||||||
|
export enum InlayHintKind {
|
||||||
|
Other = 0,
|
||||||
|
Type = 1,
|
||||||
|
Parameter = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InlayHint {
|
||||||
|
text: string
|
||||||
|
position: Position
|
||||||
|
kind: InlayHintKind
|
||||||
|
whitespaceBefore?: boolean
|
||||||
|
whitespaceAfter?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TypeScriptInlayHintsProvider implements Disposable {
|
||||||
|
public static readonly minVersion = API.v440
|
||||||
|
private readonly inlayHintsNS = workspace.createNameSpace('tsserver-inlay-hint')
|
||||||
|
|
||||||
|
private _disposables: Disposable[] = []
|
||||||
|
private _tokenSource: CancellationTokenSource | undefined = undefined
|
||||||
|
private _inlayHints: Map<string, InlayHint[]> = new Map()
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
if (this._tokenSource) {
|
||||||
|
this._tokenSource.cancel()
|
||||||
|
this._tokenSource.dispose()
|
||||||
|
this._tokenSource = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
disposeAll(this._disposables)
|
||||||
|
this._disposables = []
|
||||||
|
this._inlayHints.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private readonly client: ITypeScriptServiceClient, private readonly fileConfigurationManager: FileConfigurationManager) {
|
||||||
|
events.on('InsertLeave', async bufnr => {
|
||||||
|
const doc = workspace.getDocument(bufnr)
|
||||||
|
await this.syncAndRenderHints(doc)
|
||||||
|
}, this, this._disposables)
|
||||||
|
|
||||||
|
workspace.onDidOpenTextDocument(async e => {
|
||||||
|
const doc = workspace.getDocument(e.bufnr)
|
||||||
|
await this.syncAndRenderHints(doc)
|
||||||
|
}, this, this._disposables)
|
||||||
|
|
||||||
|
workspace.onDidChangeTextDocument(async e => {
|
||||||
|
const doc = workspace.getDocument(e.bufnr)
|
||||||
|
await this.syncAndRenderHints(doc)
|
||||||
|
}, this, this._disposables)
|
||||||
|
|
||||||
|
this.syncAndRenderHints()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncAndRenderHints(doc?: Document) {
|
||||||
|
if (!doc) doc = await workspace.document
|
||||||
|
if (!isESDocument(doc)) return
|
||||||
|
|
||||||
|
if (this._tokenSource) {
|
||||||
|
this._tokenSource.cancel()
|
||||||
|
this._tokenSource.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._tokenSource = new CancellationTokenSource()
|
||||||
|
const { token } = this._tokenSource
|
||||||
|
const range = Range.create(0, 0, doc.lineCount, doc.getline(doc.lineCount).length)
|
||||||
|
const hints = await this.provideInlayHints(doc.textDocument, range, token)
|
||||||
|
if (token.isCancellationRequested) return
|
||||||
|
|
||||||
|
await this.renderHints(doc, hints)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
this._tokenSource.cancel()
|
||||||
|
this._tokenSource.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async renderHints(doc: Document, hints: InlayHint[]) {
|
||||||
|
this._inlayHints.set(doc.uri, hints)
|
||||||
|
|
||||||
|
const chaining_hints = {}
|
||||||
|
for (const item of hints) {
|
||||||
|
const chunks: [[string, string]] = [[item.text, 'CocHintSign']]
|
||||||
|
if (chaining_hints[item.position.line] === undefined) {
|
||||||
|
chaining_hints[item.position.line] = chunks
|
||||||
|
} else {
|
||||||
|
chaining_hints[item.position.line].push([' ', 'Normal'])
|
||||||
|
chaining_hints[item.position.line].push(chunks[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.buffer.clearNamespace(this.inlayHintsNS)
|
||||||
|
Object.keys(chaining_hints).forEach(async (line) => {
|
||||||
|
await doc.buffer.setVirtualText(this.inlayHintsNS, Number(line), chaining_hints[line], {})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private inlayHintsEnabled(language: string) {
|
||||||
|
const preferences = getInlayHintsPreferences(language)
|
||||||
|
return preferences.includeInlayParameterNameHints === 'literals'
|
||||||
|
|| preferences.includeInlayParameterNameHints === 'all'
|
||||||
|
|| preferences.includeInlayEnumMemberValueHints
|
||||||
|
|| preferences.includeInlayFunctionLikeReturnTypeHints
|
||||||
|
|| preferences.includeInlayFunctionParameterTypeHints
|
||||||
|
|| preferences.includeInlayPropertyDeclarationTypeHints
|
||||||
|
|| preferences.includeInlayVariableTypeHints
|
||||||
|
}
|
||||||
|
|
||||||
|
async provideInlayHints(document: TextDocument, range: Range, token: CancellationToken): Promise<InlayHint[]> {
|
||||||
|
if (!this.inlayHintsEnabled(document.languageId)) return []
|
||||||
|
|
||||||
|
const filepath = this.client.toOpenedFilePath(document.uri)
|
||||||
|
if (!filepath) return []
|
||||||
|
|
||||||
|
const start = document.offsetAt(range.start)
|
||||||
|
const length = document.offsetAt(range.end) - start
|
||||||
|
|
||||||
|
await this.fileConfigurationManager.ensureConfigurationForDocument(document, token)
|
||||||
|
|
||||||
|
const response = await this.client.execute('provideInlayHints', { file: filepath, start, length }, token)
|
||||||
|
if (response.type !== 'response' || !response.success || !response.body) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.body.map(hint => {
|
||||||
|
return {
|
||||||
|
text: hint.text,
|
||||||
|
position: Position.create(hint.position.line - 1, hint.position.offset - 1),
|
||||||
|
kind: hint.kind && fromProtocolInlayHintKind(hint.kind),
|
||||||
|
whitespaceAfter: hint.whitespaceAfter,
|
||||||
|
whitespaceBefore: hint.whitespaceBefore,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isESDocument(doc: Document) {
|
||||||
|
if (!doc || !doc.attached) return false
|
||||||
|
return doc.filetype === 'typescript' || doc.filetype === 'javascript'
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromProtocolInlayHintKind(kind: Proto.InlayHintKind): InlayHintKind {
|
||||||
|
switch (kind) {
|
||||||
|
case 'Parameter': return InlayHintKind.Parameter
|
||||||
|
case 'Type': return InlayHintKind.Type
|
||||||
|
case 'Enum': return InlayHintKind.Other
|
||||||
|
default: return InlayHintKind.Other
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import SignatureHelpProvider from './features/signatureHelp'
|
||||||
import SemanticTokensProvider from './features/semanticTokens'
|
import SemanticTokensProvider from './features/semanticTokens'
|
||||||
import SmartSelection from './features/smartSelect'
|
import SmartSelection from './features/smartSelect'
|
||||||
import TagClosing from './features/tagClosing'
|
import TagClosing from './features/tagClosing'
|
||||||
|
import TypeScriptInlayHintsProvider from './features/inlayHints'
|
||||||
import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename'
|
import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename'
|
||||||
import { OrganizeImportsCodeActionProvider } from './organizeImports'
|
import { OrganizeImportsCodeActionProvider } from './organizeImports'
|
||||||
import TypeScriptServiceClient from './typescriptServiceClient'
|
import TypeScriptServiceClient from './typescriptServiceClient'
|
||||||
|
@ -159,6 +160,9 @@ export default class LanguageProvider {
|
||||||
if (this.client.apiVersion.gte(API.v300)) {
|
if (this.client.apiVersion.gte(API.v300)) {
|
||||||
this._register(new TagClosing(this.client, this.description.id))
|
this._register(new TagClosing(this.client, this.description.id))
|
||||||
}
|
}
|
||||||
|
if (this.client.apiVersion.gte(API.v440) && workspace.isNvim) {
|
||||||
|
this._register(new TypeScriptInlayHintsProvider(this.client, this.fileConfigurationManager))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public handles(resource: string, doc: TextDocument): boolean {
|
public handles(resource: string, doc: TextDocument): boolean {
|
||||||
|
|
|
@ -84,6 +84,7 @@ export interface TypeScriptRequestTypes {
|
||||||
'provideCallHierarchyIncomingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyIncomingCallsResponse]
|
'provideCallHierarchyIncomingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyIncomingCallsResponse]
|
||||||
'provideCallHierarchyOutgoingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyOutgoingCallsResponse]
|
'provideCallHierarchyOutgoingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyOutgoingCallsResponse]
|
||||||
'fileReferences': [Proto.FileRequestArgs, Proto.FileReferencesResponse]
|
'fileReferences': [Proto.FileRequestArgs, Proto.FileReferencesResponse]
|
||||||
|
'provideInlayHints': [Proto.InlayHintsRequestArgs, Proto.InlayHintsResponse]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITypeScriptServiceClient {
|
export interface ITypeScriptServiceClient {
|
||||||
|
|
|
@ -43,6 +43,7 @@ export default class API {
|
||||||
public static readonly v401 = API.fromSimpleString('4.0.1')
|
public static readonly v401 = API.fromSimpleString('4.0.1')
|
||||||
public static readonly v420 = API.fromSimpleString('4.2.0')
|
public static readonly v420 = API.fromSimpleString('4.2.0')
|
||||||
public static readonly v430 = API.fromSimpleString('4.3.0')
|
public static readonly v430 = API.fromSimpleString('4.3.0')
|
||||||
|
public static readonly v440 = API.fromSimpleString('4.4.0')
|
||||||
|
|
||||||
public static fromVersionString(versionString: string): API {
|
public static fromVersionString(versionString: string): API {
|
||||||
let version = semver.valid(versionString)
|
let version = semver.valid(versionString)
|
||||||
|
|
Loading…
Reference in a new issue