diff --git a/package.json b/package.json index a1824b0..5cb415a 100644 --- a/package.json +++ b/package.json @@ -678,7 +678,7 @@ "coc.nvim": "^0.0.80", "esbuild": "^0.8.29", "semver": "^7.3.2", - "vscode-languageserver-protocol": "^3.15.3", + "vscode-languageserver-protocol": "^3.16.0", "which": "^2.0.2" }, "dependencies": { diff --git a/src/server/features/callHierarchy.ts b/src/server/features/callHierarchy.ts new file mode 100644 index 0000000..90f449e --- /dev/null +++ b/src/server/features/callHierarchy.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +import { CallHierarchyProvider, TextDocument, Uri } from 'coc.nvim' +import path from "path" +import { CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, CancellationToken, Position, SymbolTag } from 'vscode-languageserver-protocol' +import type * as Proto from '../protocol' +import * as PConst from '../protocol.const' +import { ITypeScriptServiceClient } from '../typescriptService' +import API from '../utils/api' +import * as typeConverters from '../utils/typeConverters' + +export default class TypeScriptCallHierarchySupport implements CallHierarchyProvider { + public static readonly minVersion = API.v380 + + public constructor(private readonly client: ITypeScriptServiceClient) {} + + public async prepareCallHierarchy( + document: TextDocument, + position: Position, + token: CancellationToken + ): Promise { + const filepath = this.client.toOpenedFilePath(document.uri) + if (!filepath) { + return undefined + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position) + const response = await this.client.execute('prepareCallHierarchy', args, token) + if (response.type !== 'response' || !response.body) { + return undefined + } + + return Array.isArray(response.body) + ? response.body.map(fromProtocolCallHierarchyItem) + : fromProtocolCallHierarchyItem(response.body) + } + + public async provideCallHierarchyIncomingCalls(item: CallHierarchyItem, token: CancellationToken): Promise { + const filepath = this.client.toPath(item.uri) + if (!filepath) { + return undefined + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, item.selectionRange.start) + const response = await this.client.execute('provideCallHierarchyIncomingCalls', args, token) + if (response.type !== 'response' || !response.body) { + return undefined + } + + return response.body.map(fromProtocolCallHierarchyIncomingCall) + } + + public async provideCallHierarchyOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): Promise { + const filepath = this.client.toPath(item.uri) + if (!filepath) { + return undefined + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, item.selectionRange.start) + const response = await this.client.execute('provideCallHierarchyOutgoingCalls', args, token) + if (response.type !== 'response' || !response.body) { + return undefined + } + + return response.body.map(fromProtocolCallHierarchyOutgoingCall) + } +} + +function isSourceFileItem(item: Proto.CallHierarchyItem) { + return item.kind === PConst.Kind.script || item.kind === PConst.Kind.module && item.selectionSpan.start.line === 1 && item.selectionSpan.start.offset === 1 +} + +function parseKindModifier(kindModifiers: string): Set { + return new Set(kindModifiers.split(/,|\s+/g)) +} + +function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): CallHierarchyItem { + const useFileName = isSourceFileItem(item) + const name = useFileName ? path.basename(item.file) : item.name + // TODO + // const detail = useFileName ? workspace.asRelativePath(path.dirname(item.file)) : item.containerName ?? '' + const detail = item.containerName || '' + const result: CallHierarchyItem = { + name, + detail, + uri: Uri.file(item.file).toString(), + kind: typeConverters.SymbolKind.fromProtocolScriptElementKind(item.kind), + range: typeConverters.Range.fromTextSpan(item.span), + selectionRange: typeConverters.Range.fromTextSpan(item.selectionSpan) + } + + const kindModifiers = item.kindModifiers ? parseKindModifier(item.kindModifiers) : undefined + if (kindModifiers?.has(PConst.KindModifiers.depreacted)) { + result.tags = [SymbolTag.Deprecated] + } + return result +} + +function fromProtocolCallHierarchyIncomingCall(item: Proto.CallHierarchyIncomingCall): CallHierarchyIncomingCall { + return { + from: fromProtocolCallHierarchyItem(item.from), + fromRanges: item.fromSpans.map(typeConverters.Range.fromTextSpan) + } +} + +function fromProtocolCallHierarchyOutgoingCall(item: Proto.CallHierarchyOutgoingCall): CallHierarchyOutgoingCall { + return { + to: fromProtocolCallHierarchyItem(item.to), + fromRanges: item.fromSpans.map(typeConverters.Range.fromTextSpan) + } +} diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index e867d91..293775d 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -6,6 +6,7 @@ import { disposeAll, languages, TextDocument, Uri, workspace } from 'coc.nvim' import path from 'path' import { CodeActionKind, Diagnostic, DiagnosticSeverity, Disposable } from 'vscode-languageserver-protocol' import { CachedNavTreeResponse } from './features/baseCodeLensProvider' +import CallHierarchyProvider from './features/callHierarchy' import CompletionItemProvider from './features/completionItemProvider' import DefinitionProvider from './features/definitionProvider' import { DiagnosticKind } from './features/diagnostics' @@ -104,6 +105,9 @@ export default class LanguageProvider { this._register(languages.registerDocumentRangeFormatProvider(languageIds, formatProvider)) this._register(languages.registerOnTypeFormattingEditProvider(languageIds, formatProvider, [';', '}', '\n', String.fromCharCode(27)])) this._register(languages.registerCodeActionProvider(languageIds, new InstallModuleProvider(client), 'tsserver')) + if (typeof languages['registerCallHierarchyProvider'] === 'function') { + this._register(languages.registerCallHierarchyProvider(languageIds, new CallHierarchyProvider(client))) + } let { fileConfigurationManager } = this let conf = fileConfigurationManager.getLanguageConfiguration(this.id) diff --git a/src/server/protocol.const.ts b/src/server/protocol.const.ts index bff786b..546f136 100644 --- a/src/server/protocol.const.ts +++ b/src/server/protocol.const.ts @@ -44,6 +44,7 @@ export class DiagnosticCategory { export class KindModifiers { public static readonly optional = 'optional' + public static readonly depreacted = 'deprecated' public static readonly color = 'color' public static readonly dtsFile = '.d.ts' diff --git a/src/server/typescriptService.ts b/src/server/typescriptService.ts index 47af526..2b6f7b6 100644 --- a/src/server/typescriptService.ts +++ b/src/server/typescriptService.ts @@ -50,7 +50,6 @@ export interface TypeScriptRequestTypes { 'applyCodeActionCommand': [Proto.ApplyCodeActionCommandRequestArgs, Proto.ApplyCodeActionCommandResponse] 'completionEntryDetails': [Proto.CompletionDetailsRequestArgs, Proto.CompletionDetailsResponse] 'completionInfo': [Proto.CompletionsRequestArgs, Proto.CompletionInfoResponse] - 'updateOpen': [Proto.UpdateOpenRequestArgs, Proto.Response] // tslint:disable-next-line: deprecation 'completions': [Proto.CompletionsRequestArgs, Proto.CompletionsResponse] 'configure': [Proto.ConfigureRequestArguments, Proto.ConfigureResponse] @@ -80,6 +79,10 @@ export interface TypeScriptRequestTypes { 'selectionRange': [Proto.SelectionRangeRequestArgs, Proto.SelectionRangeResponse] 'signatureHelp': [Proto.SignatureHelpRequestArgs, Proto.SignatureHelpResponse] 'typeDefinition': [Proto.FileLocationRequestArgs, Proto.TypeDefinitionResponse] + 'updateOpen': [Proto.UpdateOpenRequestArgs, Proto.Response] + 'prepareCallHierarchy': [Proto.FileLocationRequestArgs, Proto.PrepareCallHierarchyResponse] + 'provideCallHierarchyIncomingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyIncomingCallsResponse] + 'provideCallHierarchyOutgoingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyOutgoingCallsResponse] 'fileReferences': [Proto.FileRequestArgs, Proto.FileReferencesResponse] } diff --git a/src/server/utils/typeConverters.ts b/src/server/utils/typeConverters.ts index a50f368..0703ce1 100644 --- a/src/server/utils/typeConverters.ts +++ b/src/server/utils/typeConverters.ts @@ -7,6 +7,7 @@ */ import * as language from 'vscode-languageserver-protocol' import Proto from '../protocol' +import * as PConst from '../protocol.const' import { ITypeScriptServiceClient } from '../typescriptService' export namespace Range { @@ -102,3 +103,33 @@ export namespace WorkspaceEdit { return { changes } } } + +export namespace SymbolKind { + export function fromProtocolScriptElementKind(kind: Proto.ScriptElementKind) { + switch (kind) { + case PConst.Kind.module: return language.SymbolKind.Module + case PConst.Kind.class: return language.SymbolKind.Class + case PConst.Kind.enum: return language.SymbolKind.Enum + case PConst.Kind.enumMember: return language.SymbolKind.EnumMember + case PConst.Kind.interface: return language.SymbolKind.Interface + case PConst.Kind.indexSignature: return language.SymbolKind.Method + case PConst.Kind.callSignature: return language.SymbolKind.Method + case PConst.Kind.method: return language.SymbolKind.Method + case PConst.Kind.memberVariable: return language.SymbolKind.Property + case PConst.Kind.memberGetAccessor: return language.SymbolKind.Property + case PConst.Kind.memberSetAccessor: return language.SymbolKind.Property + case PConst.Kind.variable: return language.SymbolKind.Variable + case PConst.Kind.let: return language.SymbolKind.Variable + case PConst.Kind.const: return language.SymbolKind.Variable + case PConst.Kind.localVariable: return language.SymbolKind.Variable + case PConst.Kind.alias: return language.SymbolKind.Variable + case PConst.Kind.function: return language.SymbolKind.Function + case PConst.Kind.localFunction: return language.SymbolKind.Function + case PConst.Kind.constructSignature: return language.SymbolKind.Constructor + case PConst.Kind.constructorImplementation: return language.SymbolKind.Constructor + case PConst.Kind.typeParameter: return language.SymbolKind.TypeParameter + case PConst.Kind.string: return language.SymbolKind.String + default: return language.SymbolKind.Variable + } + } +} diff --git a/yarn.lock b/yarn.lock index ed3d2de..6553c0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -32,23 +32,23 @@ typescript@^4.3.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw== -vscode-jsonrpc@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" - integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== +vscode-jsonrpc@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" + integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== -vscode-languageserver-protocol@^3.15.3: - version "3.15.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" - integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== +vscode-languageserver-protocol@^3.16.0: + version "3.16.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" + integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== dependencies: - vscode-jsonrpc "^5.0.1" - vscode-languageserver-types "3.15.1" + vscode-jsonrpc "6.0.0" + vscode-languageserver-types "3.16.0" -vscode-languageserver-types@3.15.1: - version "3.15.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" - integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== +vscode-languageserver-types@3.16.0: + version "3.16.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" + integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== which@^2.0.2: version "2.0.2"