Add call hierarchy support for TypeScript 3.8 (#88168)
* Add call hierarchy support for TypeScript 3.8 * Add version dependent registration for call hierarchy provider * Revert TS version, PR feedback
This commit is contained in:
parent
73bcc7671c
commit
71b60d0d22
7 changed files with 205 additions and 0 deletions
|
@ -0,0 +1,111 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
import * as typeConverters from '../utils/typeConverters';
|
||||
import API from '../utils/api';
|
||||
import { VersionDependentRegistration } from '../utils/dependentRegistration';
|
||||
import * as Proto from '../protocol';
|
||||
import * as path from 'path';
|
||||
import * as PConst from '../protocol.const';
|
||||
|
||||
class TypeScriptCallHierarchySupport implements vscode.CallHierarchyProvider {
|
||||
public static readonly minVersion = API.v380;
|
||||
public constructor(
|
||||
private readonly client: ITypeScriptServiceClient) { }
|
||||
|
||||
public async prepareCallHierarchy(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.CallHierarchyItem | vscode.CallHierarchyItem[] | undefined> {
|
||||
const filepath = this.client.toOpenedFilePath(document);
|
||||
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: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise<vscode.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(fromProtocolCallHierchyIncomingCall);
|
||||
}
|
||||
|
||||
public async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise<vscode.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(fromProtocolCallHierchyOutgoingCall);
|
||||
}
|
||||
}
|
||||
|
||||
function isSourceFileItem(item: Proto.CallHierarchyItem) {
|
||||
return item.kind === PConst.Kind.script || item.kind === PConst.Kind.module && item.selectionSpan.start.line === 0 && item.selectionSpan.start.offset === 0;
|
||||
}
|
||||
|
||||
function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): vscode.CallHierarchyItem {
|
||||
const useFileName = isSourceFileItem(item);
|
||||
const name = useFileName ? path.basename(item.file) : item.name;
|
||||
const detail = useFileName ? vscode.workspace.asRelativePath(path.dirname(item.file)) : '';
|
||||
return new vscode.CallHierarchyItem(
|
||||
typeConverters.SymbolKind.fromProtocolScriptElementKind(item.kind),
|
||||
name,
|
||||
detail,
|
||||
vscode.Uri.file(item.file),
|
||||
typeConverters.Range.fromTextSpan(item.span),
|
||||
typeConverters.Range.fromTextSpan(item.selectionSpan)
|
||||
);
|
||||
}
|
||||
|
||||
function fromProtocolCallHierchyIncomingCall(item: Proto.CallHierarchyIncomingCall): vscode.CallHierarchyIncomingCall {
|
||||
return new vscode.CallHierarchyIncomingCall(
|
||||
fromProtocolCallHierarchyItem(item.from),
|
||||
item.fromSpans.map(typeConverters.Range.fromTextSpan)
|
||||
);
|
||||
}
|
||||
|
||||
function fromProtocolCallHierchyOutgoingCall(item: Proto.CallHierarchyOutgoingCall): vscode.CallHierarchyOutgoingCall {
|
||||
return new vscode.CallHierarchyOutgoingCall(
|
||||
fromProtocolCallHierarchyItem(item.to),
|
||||
item.fromSpans.map(typeConverters.Range.fromTextSpan)
|
||||
);
|
||||
}
|
||||
|
||||
export function register(
|
||||
selector: vscode.DocumentSelector,
|
||||
client: ITypeScriptServiceClient
|
||||
) {
|
||||
return new VersionDependentRegistration(client, TypeScriptCallHierarchySupport.minVersion,
|
||||
() => vscode.languages.registerCallHierarchyProvider(selector,
|
||||
new TypeScriptCallHierarchySupport(client)));
|
||||
}
|
|
@ -78,6 +78,7 @@ export default class LanguageProvider extends Disposable {
|
|||
import('./features/signatureHelp').then(provider => this._register(provider.register(selector, this.client))),
|
||||
import('./features/tagClosing').then(provider => this._register(provider.register(selector, this.description.id, this.client))),
|
||||
import('./features/typeDefinitions').then(provider => this._register(provider.register(selector, this.client))),
|
||||
import('./features/callHierarchy').then(provider => this._register(provider.register(selector, this.client))),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,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';
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,2 +1,54 @@
|
|||
import * as Proto from 'typescript/lib/protocol';
|
||||
export = Proto;
|
||||
|
||||
declare module "typescript/lib/protocol" {
|
||||
const enum CommandTypes {
|
||||
PrepareCallHierarchy = "prepareCallHierarchy",
|
||||
ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls",
|
||||
ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls",
|
||||
}
|
||||
|
||||
interface CallHierarchyItem {
|
||||
name: string;
|
||||
kind: ScriptElementKind;
|
||||
file: string;
|
||||
span: TextSpan;
|
||||
selectionSpan: TextSpan;
|
||||
}
|
||||
|
||||
interface CallHierarchyIncomingCall {
|
||||
from: CallHierarchyItem;
|
||||
fromSpans: TextSpan[];
|
||||
}
|
||||
|
||||
interface CallHierarchyOutgoingCall {
|
||||
to: CallHierarchyItem;
|
||||
fromSpans: TextSpan[];
|
||||
}
|
||||
|
||||
interface PrepareCallHierarchyRequest extends FileLocationRequest {
|
||||
command: CommandTypes.PrepareCallHierarchy;
|
||||
}
|
||||
|
||||
interface PrepareCallHierarchyResponse extends Response {
|
||||
readonly body: CallHierarchyItem | CallHierarchyItem[];
|
||||
}
|
||||
|
||||
interface ProvideCallHierarchyIncomingCallsRequest extends FileLocationRequest {
|
||||
command: CommandTypes.ProvideCallHierarchyIncomingCalls;
|
||||
kind: ScriptElementKind;
|
||||
}
|
||||
|
||||
interface ProvideCallHierarchyIncomingCallsResponse extends Response {
|
||||
readonly body: CallHierarchyIncomingCall[];
|
||||
}
|
||||
|
||||
interface ProvideCallHierarchyOutgoingCallsRequest extends FileLocationRequest {
|
||||
command: CommandTypes.ProvideCallHierarchyOutgoingCalls;
|
||||
kind: ScriptElementKind;
|
||||
}
|
||||
|
||||
interface ProvideCallHierarchyOutgoingCallsResponse extends Response {
|
||||
readonly body: CallHierarchyOutgoingCall[];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,9 @@ interface StandardTsServerRequests {
|
|||
'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];
|
||||
}
|
||||
|
||||
interface NoResponseTsServerRequests {
|
||||
|
|
|
@ -31,6 +31,7 @@ export default class API {
|
|||
public static readonly v340 = API.fromSimpleString('3.4.0');
|
||||
public static readonly v345 = API.fromSimpleString('3.4.5');
|
||||
public static readonly v350 = API.fromSimpleString('3.5.0');
|
||||
public static readonly v380 = API.fromSimpleString('3.8.0');
|
||||
|
||||
public static fromVersionString(versionString: string): API {
|
||||
let version = semver.valid(versionString);
|
||||
|
|
|
@ -9,12 +9,18 @@
|
|||
|
||||
import * as vscode from 'vscode';
|
||||
import * as Proto from '../protocol';
|
||||
import * as PConst from '../protocol.const';
|
||||
import { ITypeScriptServiceClient } from '../typescriptService';
|
||||
|
||||
export namespace Range {
|
||||
export const fromTextSpan = (span: Proto.TextSpan): vscode.Range =>
|
||||
fromLocations(span.start, span.end);
|
||||
|
||||
export const toTextSpan = (range: vscode.Range): Proto.TextSpan => ({
|
||||
start: Position.toLocation(range.start),
|
||||
end: Position.toLocation(range.end)
|
||||
});
|
||||
|
||||
export const fromLocations = (start: Proto.Location, end: Proto.Location): vscode.Range =>
|
||||
new vscode.Range(
|
||||
Math.max(0, start.line - 1), Math.max(start.offset - 1, 0),
|
||||
|
@ -90,3 +96,33 @@ export namespace WorkspaceEdit {
|
|||
return workspaceEdit;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace SymbolKind {
|
||||
export function fromProtocolScriptElementKind(kind: Proto.ScriptElementKind) {
|
||||
switch (kind) {
|
||||
case PConst.Kind.module: return vscode.SymbolKind.Module;
|
||||
case PConst.Kind.class: return vscode.SymbolKind.Class;
|
||||
case PConst.Kind.enum: return vscode.SymbolKind.Enum;
|
||||
case PConst.Kind.enumMember: return vscode.SymbolKind.EnumMember;
|
||||
case PConst.Kind.interface: return vscode.SymbolKind.Interface;
|
||||
case PConst.Kind.indexSignature: return vscode.SymbolKind.Method;
|
||||
case PConst.Kind.callSignature: return vscode.SymbolKind.Method;
|
||||
case PConst.Kind.memberFunction: return vscode.SymbolKind.Method;
|
||||
case PConst.Kind.memberVariable: return vscode.SymbolKind.Property;
|
||||
case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Property;
|
||||
case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Property;
|
||||
case PConst.Kind.variable: return vscode.SymbolKind.Variable;
|
||||
case PConst.Kind.let: return vscode.SymbolKind.Variable;
|
||||
case PConst.Kind.const: return vscode.SymbolKind.Variable;
|
||||
case PConst.Kind.localVariable: return vscode.SymbolKind.Variable;
|
||||
case PConst.Kind.alias: return vscode.SymbolKind.Variable;
|
||||
case PConst.Kind.function: return vscode.SymbolKind.Function;
|
||||
case PConst.Kind.localFunction: return vscode.SymbolKind.Function;
|
||||
case PConst.Kind.constructSignature: return vscode.SymbolKind.Constructor;
|
||||
case PConst.Kind.constructorImplementation: return vscode.SymbolKind.Constructor;
|
||||
case PConst.Kind.typeParameter: return vscode.SymbolKind.TypeParameter;
|
||||
case PConst.Kind.string: return vscode.SymbolKind.String;
|
||||
default: return vscode.SymbolKind.Variable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue