Skip to content

Commit

Permalink
feat: callHierarchy support (#297)
Browse files Browse the repository at this point in the history
  • Loading branch information
fannheyward authored Jun 10, 2021
1 parent f2afebc commit f76e310
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 17 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
114 changes: 114 additions & 0 deletions src/server/features/callHierarchy.ts
Original file line number Diff line number Diff line change
@@ -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<CallHierarchyItem | CallHierarchyItem[] | undefined> {
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<CallHierarchyIncomingCall[] | undefined> {
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<CallHierarchyOutgoingCall[] | undefined> {
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<string> {
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)
}
}
4 changes: 4 additions & 0 deletions src/server/languageProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/server/protocol.const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
5 changes: 4 additions & 1 deletion src/server/typescriptService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
}

Expand Down
31 changes: 31 additions & 0 deletions src/server/utils/typeConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
}
}
30 changes: 15 additions & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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-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-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.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"
Expand Down

0 comments on commit f76e310

Please sign in to comment.