Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Call hierarchy API proposal #80713

Merged
merged 6 commits into from
Sep 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 62 additions & 26 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,49 +18,85 @@ declare module 'vscode' {

//#region Joh - call hierarchy

export enum CallHierarchyDirection {
CallsFrom = 1,
CallsTo = 2,
}

export class CallHierarchyItem {
kind: SymbolKind;
/**
* The name of this item.
*/
name: string;

/**
* The kind of this item.
*/
kind: SymbolKind;

/**
* Tags for this item.
*/
tags?: ReadonlyArray<SymbolTag>;

/**
* More detail for this item, e.g. the signature of a function.
*/
detail?: string;

/**
* The resource identifier of this item.
*/
uri: Uri;

/**
* The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code.
*/
range: Range;

/**
* The range that should be selected and reveal when this symbol is being picked, e.g. the name of a function.
* Must be contained by the [`range`](#CallHierarchyItem.range).
*/
selectionRange: Range;

constructor(kind: SymbolKind, name: string, detail: string, uri: Uri, range: Range, selectionRange: Range);
}

export class CallHierarchyIncomingCall {
source: CallHierarchyItem;
sourceRanges: Range[];
constructor(item: CallHierarchyItem, sourceRanges: Range[]);
}

export class CallHierarchyOutgoingCall {
sourceRanges: Range[];
target: CallHierarchyItem;
constructor(item: CallHierarchyItem, sourceRanges: Range[]);
}

export interface CallHierarchyItemProvider {

/**
* Given a document and position compute a call hierarchy item. This is justed as
* anchor for call hierarchy and then `resolveCallHierarchyItem` is being called.
* Provide a list of callers for the provided item, e.g. all function calling a function.
*
* @param target
* @param token
* @returns
*/
provideCallHierarchyItem(
document: TextDocument,
position: Position,
token: CancellationToken
): ProviderResult<CallHierarchyItem>;
provideCallHierarchyIncomingCalls(target: CallHierarchyItem, token: CancellationToken): ProviderResult<CallHierarchyIncomingCall[]>;

/**
* Resolve a call hierarchy item, e.g. compute all calls from or to a function.
* The result is an array of item/location-tuples. The location in the returned tuples
* is always relative to the "caller" with the caller either being the provided item or
* the returned item.
* Provide a list of calls for the provided item, e.g. all functions call from a function.
*
* @param item A call hierarchy item previously returned from `provideCallHierarchyItem` or `resolveCallHierarchyItem`
* @param direction Resolve calls from a function or calls to a function
* @param token A cancellation token
*/
resolveCallHierarchyItem(
item: CallHierarchyItem,
direction: CallHierarchyDirection,
token: CancellationToken
): ProviderResult<[CallHierarchyItem, Location[]][]>;
* @param source
* @param token
* @returns
*/
provideCallHierarchyOutgoingCalls(source: CallHierarchyItem, token: CancellationToken): ProviderResult<CallHierarchyOutgoingCall[]>;

/**
* todo@joh not needed?
*
* Given a document and position compute a call hierarchy item. This is justed as
* anchor for call hierarchy and then `resolveCallHierarchyItem` is being called.
*/
resolveCallHierarchyItem(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<CallHierarchyItem>;
}

export namespace languages {
Expand Down
40 changes: 25 additions & 15 deletions src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as search from 'vs/workbench/contrib/search/common/search';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Position as EditorPosition } from 'vs/editor/common/core/position';
import { Range as EditorRange, IRange } from 'vs/editor/common/core/range';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyDto, ISuggestDataDto, ICodeActionDto } from '../common/extHost.protocol';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto } from '../common/extHost.protocol';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration';
import { IModeService } from 'vs/editor/common/services/modeService';
Expand Down Expand Up @@ -111,7 +111,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
return <modes.ILink>data;
}

private static _reviveCallHierarchyItemDto(data: ICallHierarchyDto | undefined): callh.CallHierarchyItem {
private static _reviveCallHierarchyItemDto(data: ICallHierarchyItemDto | undefined): callh.CallHierarchyItem {
if (data) {
data.uri = URI.revive(data.uri);
}
Expand Down Expand Up @@ -494,21 +494,31 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha

$registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void {
this._registrations.set(handle, callh.CallHierarchyProviderRegistry.register(selector, {
provideCallHierarchyItem: (document, position, token) => {
return this._proxy.$provideCallHierarchyItem(handle, document.uri, position, token).then(MainThreadLanguageFeatures._reviveCallHierarchyItemDto);
resolveCallHierarchyItem: (document, position, token) => {
return this._proxy.$resolveCallHierarchyItem(handle, document.uri, position, token).then(MainThreadLanguageFeatures._reviveCallHierarchyItemDto);
},
resolveCallHierarchyItem: (item, direction, token) => {
return this._proxy.$resolveCallHierarchyItem(handle, item, direction, token).then(data => {
if (data) {
for (let i = 0; i < data.length; i++) {
const [item, locations] = data[i];
data[i] = [
MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item),
MainThreadLanguageFeatures._reviveLocationDto(locations)
];
}
provideOutgoingCalls: async (item, token) => {
const outgoing = await this._proxy.$provideCallHierarchyOutgoingCalls(handle, item, token);
if (!outgoing) {
return outgoing;
}
return outgoing.map(([item, sourceRanges]): callh.OutgoingCall => {
return {
target: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item),
sourceRanges
}
});
},
provideIncomingCalls: async (item, token) => {
const incoming = await this._proxy.$provideCallHierarchyIncomingCalls(handle, item, token);
if (!incoming) {
return incoming;
}
return incoming.map(([item, sourceRanges]): callh.IncomingCall => {
return {
source: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item),
sourceRanges
}
return data as [callh.CallHierarchyItem, modes.Location[]][];
});
}
}));
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
ViewColumn: extHostTypes.ViewColumn,
WorkspaceEdit: extHostTypes.WorkspaceEdit,
// proposed
CallHierarchyDirection: extHostTypes.CallHierarchyDirection,
CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall,
CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall,
CallHierarchyItem: extHostTypes.CallHierarchyItem,
Decoration: extHostTypes.Decoration
};
Expand Down
7 changes: 4 additions & 3 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,7 @@ export interface ICodeLensDto {
command?: ICommandDto;
}

export interface ICallHierarchyDto {
export interface ICallHierarchyItemDto {
_id: number;
kind: modes.SymbolKind;
name: string;
Expand Down Expand Up @@ -1111,8 +1111,9 @@ export interface ExtHostLanguageFeaturesShape {
$provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise<modes.IColorPresentation[] | undefined>;
$provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise<modes.FoldingRange[] | undefined>;
$provideSelectionRanges(handle: number, resource: UriComponents, positions: IPosition[], token: CancellationToken): Promise<modes.SelectionRange[][]>;
$provideCallHierarchyItem(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<ICallHierarchyDto | undefined>;
$resolveCallHierarchyItem(handle: number, item: callHierarchy.CallHierarchyItem, direction: callHierarchy.CallHierarchyDirection, token: CancellationToken): Promise<[ICallHierarchyDto, modes.Location[]][]>;
$resolveCallHierarchyItem(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<ICallHierarchyItemDto | undefined>;
$provideCallHierarchyIncomingCalls(handle: number, target: callHierarchy.CallHierarchyItem, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined>;
$provideCallHierarchyOutgoingCalls(handle: number, source: callHierarchy.CallHierarchyItem, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined>;
}

export interface ExtHostQuickOpenShape {
Expand Down
68 changes: 39 additions & 29 deletions src/vs/workbench/api/common/extHostLanguageFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics';
import { asPromise } from 'vs/base/common/async';
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, IRawColorInfo, IMainContext, IdObject, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILanguageConfigurationDto, IWorkspaceSymbolDto, ISuggestResultDto, IWorkspaceSymbolsDto, ICodeActionDto, IDocumentFilterDto, IWorkspaceEditDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICodeLensDto, ISuggestDataDto, ILinksListDto, ChainedCacheId, ICodeLensListDto, ICodeActionListDto, ISignatureHelpDto, ISignatureHelpContextDto } from './extHost.protocol';
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, IRawColorInfo, IMainContext, IdObject, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILanguageConfigurationDto, IWorkspaceSymbolDto, ISuggestResultDto, IWorkspaceSymbolsDto, ICodeActionDto, IDocumentFilterDto, IWorkspaceEditDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICodeLensDto, ISuggestDataDto, ILinksListDto, ChainedCacheId, ICodeLensListDto, ICodeActionListDto, ISignatureHelpDto, ISignatureHelpContextDto, ICallHierarchyItemDto } from './extHost.protocol';
import { regExpLeadsToEndlessLoop, regExpFlags } from 'vs/base/common/strings';
import { IPosition } from 'vs/editor/common/core/position';
import { IRange, Range as EditorRange } from 'vs/editor/common/core/range';
Expand Down Expand Up @@ -1011,42 +1011,48 @@ class SelectionRangeAdapter {

class CallHierarchyAdapter {

// todo@joh keep object (heap service, lifecycle)
private readonly _cache = new LRUCache<number, vscode.CallHierarchyItem>(1000, 0.8);
// todo@joh keep object (add managed lifecycle)
private readonly _cache = new LRUCache<number, vscode.CallHierarchyItem>(3000, 0.8);
private _idPool = 0;

constructor(
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.CallHierarchyItemProvider
) { }

provideCallHierarchyItem(resource: URI, pos: IPosition, token: CancellationToken): Promise<undefined | callHierarchy.CallHierarchyItem> {
async resolveCallHierarchyItem(resource: URI, pos: IPosition, token: CancellationToken): Promise<undefined | callHierarchy.CallHierarchyItem> {
const document = this._documents.getDocument(resource);
const position = typeConvert.Position.to(pos);

return asPromise(() => this._provider.provideCallHierarchyItem(document, position, token)).then(item => {
if (!item) {
return undefined;
}
return this._fromItem(item);
});
const item = await this._provider.resolveCallHierarchyItem(document, position, token);
if (!item) {
return undefined;
}
return this._fromItem(item);
}

resolveCallHierarchyItem(item: callHierarchy.CallHierarchyItem, direction: callHierarchy.CallHierarchyDirection, token: CancellationToken): Promise<[callHierarchy.CallHierarchyItem, modes.Location[]][]> {
return asPromise(() => this._provider.resolveCallHierarchyItem(
this._cache.get(item._id)!,
direction as number, token) // todo@joh proper convert
).then(data => {
if (!data) {
return [];
}
return data.map(tuple => {
return <[callHierarchy.CallHierarchyItem, modes.Location[]]>[
this._fromItem(tuple[0]),
tuple[1].map(typeConvert.location.from)
];
});
});
async provideCallsTo(target: callHierarchy.CallHierarchyItem, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined> {
const item = this._cache.get(target._id);
if (!item) {
return undefined;
}
const calls = await this._provider.provideCallHierarchyIncomingCalls(item, token);
if (!calls) {
return undefined;
}
return calls.map(call => (<[ICallHierarchyItemDto, IRange[]]>[this._fromItem(call.source), call.sourceRanges.map(typeConvert.Range.from)]))
}

async provideCallsFrom(source: callHierarchy.CallHierarchyItem, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined> {
const item = this._cache.get(source._id);
if (!item) {
return undefined;
}
const calls = await this._provider.provideCallHierarchyOutgoingCalls(item, token);
if (!calls) {
return undefined;
}
return calls.map(call => (<[ICallHierarchyItemDto, IRange[]]>[this._fromItem(call.target), call.sourceRanges.map(typeConvert.Range.from)]))
}

private _fromItem(item: vscode.CallHierarchyItem, _id: number = this._idPool++): callHierarchy.CallHierarchyItem {
Expand Down Expand Up @@ -1496,12 +1502,16 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
return this._createDisposable(handle);
}

$provideCallHierarchyItem(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<undefined | callHierarchy.CallHierarchyItem> {
return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallHierarchyItem(URI.revive(resource), position, token), undefined);
$resolveCallHierarchyItem(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<ICallHierarchyItemDto | undefined> {
return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.resolveCallHierarchyItem(URI.revive(resource), position, token), undefined);
}

$provideCallHierarchyIncomingCalls(handle: number, target: callHierarchy.CallHierarchyItem, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined> {
return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsTo(target, token), undefined);
}

$resolveCallHierarchyItem(handle: number, item: callHierarchy.CallHierarchyItem, direction: callHierarchy.CallHierarchyDirection, token: CancellationToken): Promise<[callHierarchy.CallHierarchyItem, modes.Location[]][]> {
return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.resolveCallHierarchyItem(item, direction, token), []);
$provideCallHierarchyOutgoingCalls(handle: number, source: callHierarchy.CallHierarchyItem, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined> {
return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsFrom(source, token), undefined);
}

// --- configuration
Expand Down
27 changes: 21 additions & 6 deletions src/vs/workbench/api/common/extHostTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1146,12 +1146,6 @@ export class SelectionRange {
}
}


export enum CallHierarchyDirection {
CallsFrom = 1,
CallsTo = 2,
}

export class CallHierarchyItem {
kind: SymbolKind;
name: string;
Expand All @@ -1170,6 +1164,27 @@ export class CallHierarchyItem {
}
}

export class CallHierarchyIncomingCall {

source: CallHierarchyItem;
sourceRanges: Range[];

constructor(item: CallHierarchyItem, sourceRanges: Range[]) {
this.sourceRanges = sourceRanges;
this.source = item;
}
}
export class CallHierarchyOutgoingCall {

target: CallHierarchyItem;
sourceRanges: Range[];

constructor(item: CallHierarchyItem, sourceRanges: Range[]) {
this.sourceRanges = sourceRanges;
this.target = item;
}
}

@es5ClassCompat
export class CodeLens {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class CallHierarchyController extends Disposable implements IEditorContribution
this._editor,
position,
provider,
CallHierarchyDirection.CallsTo
CallHierarchyDirection.CallsFrom
);

widget.showLoading();
Expand All @@ -93,7 +93,7 @@ class CallHierarchyController extends Disposable implements IEditorContribution
this._sessionDispose.push({ dispose() { cancel.cancel(); } });
this._sessionDispose.push(widget);

Promise.resolve(provider.provideCallHierarchyItem(model, position, cancel.token)).then(item => {
Promise.resolve(provider.resolveCallHierarchyItem(model, position, cancel.token)).then(item => {
if (cancel.token.isCancellationRequested) {
return;
}
Expand Down
Loading