提交 15bcf1a8 编写于 作者: J Johannes Rieken

call hierarchy shows root node, also update API proposal for this, #81753

上级 7eb32121
......@@ -72,24 +72,17 @@ declare module 'vscode' {
export interface CallHierarchyItemProvider {
prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<CallHierarchyItem>;
/**
* Provide a list of callers for the provided item, e.g. all function calling a function.
*/
provideCallHierarchyIncomingCalls(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<CallHierarchyIncomingCall[]>;
provideCallHierarchyIncomingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<CallHierarchyIncomingCall[]>;
/**
* Provide a list of calls for the provided item, e.g. all functions call from a function.
*/
provideCallHierarchyOutgoingCalls(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<CallHierarchyOutgoingCall[]>;
// todo@joh this could return as 'prepareCallHierarchy' (similar to the RenameProvider#prepareRename)
//
// /**
// *
// * 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>;
provideCallHierarchyOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<CallHierarchyOutgoingCall[]>;
}
export namespace languages {
......
......@@ -496,8 +496,20 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
$registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void {
this._registrations.set(handle, callh.CallHierarchyProviderRegistry.register(selector, {
provideOutgoingCalls: async (model, position, token) => {
const outgoing = await this._proxy.$provideCallHierarchyOutgoingCalls(handle, model.uri, position, token);
prepareCallHierarchy: async (document, position, token) => {
const result = await this._proxy.$prepareCallHierarchy(handle, document.uri, position, token);
if (!result) {
return undefined;
}
return {
dispose: () => this._proxy.$releaseCallHierarchy(handle, result.sessionId),
root: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(result.root)
};
},
provideOutgoingCalls: async (item, token) => {
const outgoing = await this._proxy.$provideCallHierarchyOutgoingCalls(handle, item.id, token);
if (!outgoing) {
return outgoing;
}
......@@ -508,8 +520,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
};
});
},
provideIncomingCalls: async (model, position, token) => {
const incoming = await this._proxy.$provideCallHierarchyIncomingCalls(handle, model.uri, position, token);
provideIncomingCalls: async (item, token) => {
const incoming = await this._proxy.$provideCallHierarchyIncomingCalls(handle, item.id, token);
if (!incoming) {
return incoming;
}
......
......@@ -1104,6 +1104,7 @@ export interface ICodeLensDto {
}
export interface ICallHierarchyItemDto {
id: string;
kind: modes.SymbolKind;
name: string;
detail?: string;
......@@ -1146,8 +1147,10 @@ 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[][]>;
$provideCallHierarchyIncomingCalls(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined>;
$provideCallHierarchyOutgoingCalls(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined>;
$prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<{ sessionId: string, root: ICallHierarchyItemDto } | undefined>;
$provideCallHierarchyIncomingCalls(handle: number, itemId: string, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined>;
$provideCallHierarchyOutgoingCalls(handle: number, itemId: string, token: CancellationToken): Promise<[ICallHierarchyItemDto, IRange[]][] | undefined>;
$releaseCallHierarchy(handle: number, sessionId: string): void;
}
export interface ExtHostQuickOpenShape {
......
......@@ -1034,29 +1034,77 @@ class SelectionRangeAdapter {
class CallHierarchyAdapter {
private _idPool: number = 0;
private readonly _cache = new Map<string, vscode.CallHierarchyItem[]>();
constructor(
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.CallHierarchyItemProvider
) { }
async provideCallsTo(uri: URI, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> {
async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise<{ sessionId: string, root: extHostProtocol.ICallHierarchyItemDto } | undefined> {
const doc = this._documents.getDocument(uri);
const pos = typeConvert.Position.to(position);
const calls = await this._provider.provideCallHierarchyIncomingCalls(doc, pos, token);
const item = await this._provider.prepareCallHierarchy(doc, pos, token);
if (!item) {
return undefined;
}
const sessionId = String.fromCharCode(this._idPool++);
this._cache.set(sessionId, []);
return { sessionId, root: this._cacheAndConvertItem(sessionId, item) };
}
async provideCallsTo(itemId: string, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> {
const item = this._itemFromCache(itemId);
if (!item) {
throw new Error('missing call hierarchy item');
}
const calls = await this._provider.provideCallHierarchyIncomingCalls(item, token);
if (!calls) {
return undefined;
}
return calls.map(call => (<[extHostProtocol.ICallHierarchyItemDto, IRange[]]>[typeConvert.CallHierarchyItem.from(call.from), call.fromRanges.map(typeConvert.Range.from)]));
return calls.map(call => (<[extHostProtocol.ICallHierarchyItemDto, IRange[]]>[this._cacheAndConvertItem(itemId, call.from), call.fromRanges.map(typeConvert.Range.from)]));
}
async provideCallsFrom(uri: URI, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> {
const doc = this._documents.getDocument(uri);
const pos = typeConvert.Position.to(position);
const calls = await this._provider.provideCallHierarchyOutgoingCalls(doc, pos, token);
async provideCallsFrom(itemId: string, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> {
const item = this._itemFromCache(itemId);
if (!item) {
throw new Error('missing call hierarchy item');
}
const calls = await this._provider.provideCallHierarchyOutgoingCalls(item, token);
if (!calls) {
return undefined;
}
return calls.map(call => (<[extHostProtocol.ICallHierarchyItemDto, IRange[]]>[typeConvert.CallHierarchyItem.from(call.to), call.fromRanges.map(typeConvert.Range.from)]));
return calls.map(call => (<[extHostProtocol.ICallHierarchyItemDto, IRange[]]>[this._cacheAndConvertItem(itemId, call.to), call.fromRanges.map(typeConvert.Range.from)]));
}
releaseSession(sessionId: string): void {
this._cache.delete(sessionId);
}
private _cacheAndConvertItem(sessionId: string, item: vscode.CallHierarchyItem): extHostProtocol.ICallHierarchyItemDto {
const array = this._cache.get(sessionId.charAt(0))!;
const dto: extHostProtocol.ICallHierarchyItemDto = {
id: sessionId + String.fromCharCode(array.length),
name: item.name,
detail: item.detail,
kind: typeConvert.SymbolKind.from(item.kind),
uri: item.uri,
range: typeConvert.Range.from(item.range),
selectionRange: typeConvert.Range.from(item.selectionRange),
};
array.push(item);
return dto;
}
private _itemFromCache(itemId: string): vscode.CallHierarchyItem | undefined {
const sessionId = itemId.charAt(0);
const array = this._cache.get(sessionId);
if (!array) {
return undefined;
}
return array[itemId.charCodeAt(1)];
}
}
......@@ -1492,12 +1540,20 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
return this._createDisposable(handle);
}
$provideCallHierarchyIncomingCalls(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> {
return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsTo(URI.revive(resource), position, token), undefined);
$prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<{ sessionId: string, root: extHostProtocol.ICallHierarchyItemDto } | undefined> {
return this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.prepareSession(URI.revive(resource), position, token)), undefined);
}
$provideCallHierarchyIncomingCalls(handle: number, itemId: string, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> {
return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsTo(itemId, token), undefined);
}
$provideCallHierarchyOutgoingCalls(handle: number, itemId: string, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> {
return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsFrom(itemId, token), undefined);
}
$provideCallHierarchyOutgoingCalls(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<[extHostProtocol.ICallHierarchyItemDto, IRange[]][] | undefined> {
return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsFrom(URI.revive(resource), position, token), undefined);
$releaseCallHierarchy(handle: number, sessionId: string): void {
this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.releaseSession(sessionId)), undefined);
}
// --- configuration
......
......@@ -627,17 +627,6 @@ export namespace DocumentSymbol {
export namespace CallHierarchyItem {
export function from(item: vscode.CallHierarchyItem): extHostProtocol.ICallHierarchyItemDto {
return {
name: item.name,
detail: item.detail,
kind: SymbolKind.from(item.kind),
uri: item.uri,
range: Range.from(item.range),
selectionRange: Range.from(item.selectionRange),
};
}
export function to(item: extHostProtocol.ICallHierarchyItemDto): vscode.CallHierarchyItem {
return new types.CallHierarchyItem(
SymbolKind.to(item.kind),
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { CallHierarchyProviderRegistry, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy';
import { CallHierarchyProviderRegistry, CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { CallHierarchyTreePeekWidget } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek';
......@@ -18,7 +18,6 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { PeekContext } from 'vs/editor/contrib/referenceSearch/peekViewWidget';
import { CallHierarchyRoot } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
const _ctxHasCompletionItemProvider = new RawContextKey<boolean>('editorHasCallHierarchyProvider', false);
......@@ -66,10 +65,9 @@ class CallHierarchyController implements IEditorContribution {
return;
}
const model = this._editor.getModel();
const document = this._editor.getModel();
const position = this._editor.getPosition();
const [provider] = CallHierarchyProviderRegistry.ordered(model);
if (!provider) {
if (!CallHierarchyProviderRegistry.has(document)) {
return;
}
......@@ -80,27 +78,34 @@ class CallHierarchyController implements IEditorContribution {
CallHierarchyTreePeekWidget,
this._editor,
position,
provider,
direction
);
widget.showLoading();
this._ctxIsVisible.set(true);
const cancel = new CancellationTokenSource();
const cts = new CancellationTokenSource();
this._sessionDisposables.add(widget.onDidClose(() => {
this.endCallHierarchy();
this._storageService.store(CallHierarchyController._StorageDirection, widget.direction, StorageScope.GLOBAL);
}));
this._sessionDisposables.add({ dispose() { cancel.cancel(); } });
this._sessionDisposables.add({ dispose() { cts.dispose(true); } });
this._sessionDisposables.add(widget);
const root = CallHierarchyRoot.fromEditor(this._editor);
if (root) {
widget.showItem(root);
} else {
widget.showMessage(localize('no.item', "No results"));
try {
const model = await CallHierarchyModel.create(document, position, cts.token);
if (cts.token.isCancellationRequested) {
return; // nothing
} else if (model) {
this._sessionDisposables.add(model);
widget.showItem(model);
} else {
widget.showMessage(localize('no.item', "No results"));
}
} catch (e) {
widget.showMessage(localize('error', "Failed to show call hierarchy"));
console.error(e);
}
}
......
......@@ -20,6 +20,7 @@ export const enum CallHierarchyDirection {
}
export interface CallHierarchyItem {
id: string;
kind: SymbolKind;
name: string;
detail?: string;
......@@ -38,46 +39,79 @@ export interface OutgoingCall {
to: CallHierarchyItem;
}
export interface CallHierarchySession {
root: CallHierarchyItem;
dispose(): void;
}
export interface CallHierarchyProvider {
provideIncomingCalls(document: ITextModel, postion: IPosition, token: CancellationToken): ProviderResult<IncomingCall[]>;
prepareCallHierarchy(document: ITextModel, position: IPosition, token: CancellationToken): ProviderResult<CallHierarchySession>;
provideIncomingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<IncomingCall[]>;
provideOutgoingCalls(document: ITextModel, postion: IPosition, token: CancellationToken): ProviderResult<OutgoingCall[]>;
provideOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<OutgoingCall[]>;
}
export const CallHierarchyProviderRegistry = new LanguageFeatureRegistry<CallHierarchyProvider>();
export async function provideIncomingCalls(model: ITextModel, position: IPosition, token: CancellationToken): Promise<IncomingCall[]> {
const [provider] = CallHierarchyProviderRegistry.ordered(model);
if (!provider) {
return [];
}
try {
const result = await provider.provideIncomingCalls(model, position, token);
if (isNonEmptyArray(result)) {
return result;
export class CallHierarchyModel {
static async create(model: ITextModel, position: IPosition, token: CancellationToken): Promise<CallHierarchyModel | undefined> {
const [provider] = CallHierarchyProviderRegistry.ordered(model);
if (!provider) {
return undefined;
}
const session = await provider.prepareCallHierarchy(model, position, token);
if (!session) {
return undefined;
}
} catch (e) {
onUnexpectedExternalError(e);
return new CallHierarchyModel(provider, session);
}
return [];
}
export async function provideOutgoingCalls(model: ITextModel, position: IPosition, token: CancellationToken): Promise<OutgoingCall[]> {
const [provider] = CallHierarchyProviderRegistry.ordered(model);
if (!provider) {
private constructor(
readonly provider: CallHierarchyProvider,
readonly session: CallHierarchySession,
readonly root = session.root
) { }
dispose(): void {
this.session.dispose();
}
async resolveIncomingCalls(item: CallHierarchyItem, token: CancellationToken): Promise<IncomingCall[]> {
try {
const result = await this.provider.provideIncomingCalls(item, token);
if (isNonEmptyArray(result)) {
return result;
}
} catch (e) {
onUnexpectedExternalError(e);
}
return [];
}
try {
const result = await provider.provideOutgoingCalls(model, position, token);
if (isNonEmptyArray(result)) {
return result;
async resolveOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): Promise<OutgoingCall[]> {
try {
const result = await this.provider.provideOutgoingCalls(item, token);
if (isNonEmptyArray(result)) {
return result;
}
} catch (e) {
onUnexpectedExternalError(e);
}
} catch (e) {
onUnexpectedExternalError(e);
return [];
}
return [];
}
export async function provideIncomingCalls(model: ITextModel, position: IPosition, token: CancellationToken): Promise<IncomingCall[]> {
throw new Error();
}
export async function provideOutgoingCalls(model: ITextModel, position: IPosition, token: CancellationToken): Promise<OutgoingCall[]> {
throw new Error();
}
registerDefaultLanguageCommand('_executeCallHierarchyIncomingCalls', async (model, position) => provideIncomingCalls(model, position, CancellationToken.None));
......
......@@ -7,7 +7,7 @@ import 'vs/css!./media/callHierarchy';
import { PeekViewWidget, IPeekViewService } from 'vs/editor/contrib/referenceSearch/peekViewWidget';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CallHierarchyProvider, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy';
import { CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy';
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { FuzzyScore } from 'vs/base/common/filters';
import * as callHTree from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree';
......@@ -31,7 +31,7 @@ import { Action } from 'vs/base/common/actions';
import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { Color } from 'vs/base/common/color';
import { TreeMouseEventTarget } from 'vs/base/browser/ui/tree/tree';
import { TreeMouseEventTarget, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { URI } from 'vs/base/common/uri';
const enum State {
......@@ -94,7 +94,7 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget {
private _parent!: HTMLElement;
private _message!: HTMLElement;
private _splitView!: SplitView;
private _tree!: WorkbenchAsyncDataTree<callHTree.CallHierarchyRoot, callHTree.Call, FuzzyScore>;
private _tree!: WorkbenchAsyncDataTree<CallHierarchyModel, callHTree.Call, FuzzyScore>;
private _treeViewStates = new Map<CallHierarchyDirection, IAsyncDataTreeViewState>();
private _editor!: EmbeddedCodeEditorWidget;
private _dim!: Dimension;
......@@ -103,7 +103,6 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget {
constructor(
editor: ICodeEditor,
private readonly _where: IPosition,
private readonly _provider: CallHierarchyProvider,
private _direction: CallHierarchyDirection,
@IThemeService themeService: IThemeService,
@IPeekViewService private readonly _peekViewService: IPeekViewService,
......@@ -209,7 +208,7 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget {
treeContainer,
new callHTree.VirtualDelegate(),
[this._instantiationService.createInstance(callHTree.CallRenderer)],
this._instantiationService.createInstance(callHTree.DataSource, this._provider, () => this._direction),
this._instantiationService.createInstance(callHTree.DataSource, () => this._direction),
options
);
......@@ -270,7 +269,12 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget {
let previewUri: URI;
if (this._direction === CallHierarchyDirection.CallsFrom) {
// outgoing calls: show caller and highlight focused calls
previewUri = element.parent ? element.parent.item.uri : this._tree.getInput()!.model.uri;
const parent = this._tree.getParentElement(element);
if (parent instanceof callHTree.Call) {
previewUri = parent.item.uri;
} else {
previewUri = parent.root.uri;
}
} else {
// incoming calls: show caller and highlight focused calls
previewUri = element.item.uri;
......@@ -297,8 +301,8 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget {
// update: title
const title = this._direction === CallHierarchyDirection.CallsFrom
? localize('callFrom', "Calls from '{0}'", this._tree.getInput()!.word)
: localize('callsTo', "Callers of '{0}'", this._tree.getInput()!.word);
? localize('callFrom', "Calls from '{0}'", this._tree.getInput()!.root.name)
: localize('callsTo', "Callers of '{0}'", this._tree.getInput()!.root.name);
this.setTitle(title);
}));
......@@ -361,33 +365,34 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget {
this._message.focus();
}
async showItem(item: callHTree.CallHierarchyRoot): Promise<void> {
async showItem(item: CallHierarchyModel): Promise<void> {
this._show();
const viewState = this._treeViewStates.get(this._direction);
await this._tree.setInput(item, viewState);
if (this._tree.getNode(item).children.length === 0) {
const root = <ITreeNode<callHTree.Call>>this._tree.getNode(item).children[0];
await this._tree.expand(root.element);
if (root.children.length === 0) {
//
this.showMessage(this._direction === CallHierarchyDirection.CallsFrom
? localize('empt.callsFrom', "No calls from '{0}'", item.word)
: localize('empt.callsTo', "No callers of '{0}'", item.word));
? localize('empt.callsFrom', "No calls from '{0}'", item.root.name)
: localize('empt.callsTo', "No callers of '{0}'", item.root.name));
} else {
this._parent.dataset['state'] = State.Data;
this._tree.domFocus();
if (!viewState) {
this._tree.focusFirst();
}
this._tree.setFocus([root.children[0].element]);
}
if (!this._changeDirectionAction) {
const changeDirection = (newDirection: CallHierarchyDirection) => {
const changeDirection = async (newDirection: CallHierarchyDirection) => {
if (this._direction !== newDirection) {
this._treeViewStates.set(this._direction, this._tree.getViewState());
this._direction = newDirection;
this._tree.setFocus([]);
this.showItem(this._tree.getInput()!);
await this.showItem(item);
}
};
this._changeDirectionAction = new ChangeHierarchyDirectionAction(this._direction, changeDirection);
......
......@@ -4,105 +4,72 @@
*--------------------------------------------------------------------------------------------*/
import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { CallHierarchyItem, CallHierarchyProvider, CallHierarchyDirection, provideOutgoingCalls, provideIncomingCalls } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy';
import { CallHierarchyItem, CallHierarchyDirection, CallHierarchyModel, } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { SymbolKinds, Location } from 'vs/editor/common/modes';
import { hash } from 'vs/base/common/hash';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
import { IPosition } from 'vs/editor/common/core/position';
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import * as dom from 'vs/base/browser/dom';
export class Call {
constructor(
readonly item: CallHierarchyItem,
readonly locations: Location[],
readonly parent: Call | undefined
readonly model: CallHierarchyModel
) { }
}
export class CallHierarchyRoot {
static fromEditor(editor: IActiveCodeEditor): CallHierarchyRoot | undefined {
const model = editor.getModel();
const position = editor.getPosition();
const wordInfo = model.getWordAtPosition(position);
return wordInfo
? new CallHierarchyRoot(model, position, wordInfo.word)
: undefined;
}
constructor(
readonly model: ITextModel,
readonly position: IPosition,
readonly word: string
) { }
}
export class DataSource implements IAsyncDataSource<CallHierarchyRoot, Call> {
export class DataSource implements IAsyncDataSource<CallHierarchyModel, Call> {
constructor(
public provider: CallHierarchyProvider,
public getDirection: () => CallHierarchyDirection,
@ITextModelService private readonly _modelService: ITextModelService,
) { }
hasChildren(): boolean {
return true;
}
async getChildren(element: CallHierarchyRoot | Call): Promise<Call[]> {
async getChildren(element: CallHierarchyModel | Call): Promise<Call[]> {
if (element instanceof CallHierarchyModel) {
return [new Call(element.root, [], element)];
}
const results: Call[] = [];
if (element instanceof CallHierarchyRoot) {
if (this.getDirection() === CallHierarchyDirection.CallsFrom) {
await this._getOutgoingCalls(element.model, element.position, results);
} else {
await this._getIncomingCalls(element.model, element.position, results);
}
if (this.getDirection() === CallHierarchyDirection.CallsFrom) {
await this._getOutgoingCalls(element.model, element.item, results);
} else {
const reference = await this._modelService.createModelReference(element.item.uri);
const position = Range.lift(element.item.selectionRange).getStartPosition();
if (this.getDirection() === CallHierarchyDirection.CallsFrom) {
await this._getOutgoingCalls(reference.object.textEditorModel, position, results, element);
} else {
await this._getIncomingCalls(reference.object.textEditorModel, position, results, element);
}
reference.dispose();
await this._getIncomingCalls(element.model, element.item, results);
}
return results;
}
private async _getOutgoingCalls(model: ITextModel, position: IPosition, bucket: Call[], parent?: Call): Promise<void> {
const outgoingCalls = await provideOutgoingCalls(model, position, CancellationToken.None);
private async _getOutgoingCalls(model: CallHierarchyModel, item: CallHierarchyItem, bucket: Call[]): Promise<void> {
const outgoingCalls = await model.resolveOutgoingCalls(item, CancellationToken.None);
for (const call of outgoingCalls) {
bucket.push(new Call(
call.to,
call.fromRanges.map(range => ({ range, uri: model.uri })),
parent
call.fromRanges.map(range => ({ range, uri: item.uri })),
model
));
}
}
private async _getIncomingCalls(model: ITextModel, position: IPosition, bucket: Call[], parent?: Call): Promise<void> {
const incomingCalls = await provideIncomingCalls(model, position, CancellationToken.None);
private async _getIncomingCalls(model: CallHierarchyModel, item: CallHierarchyItem, bucket: Call[]): Promise<void> {
const incomingCalls = await model.resolveIncomingCalls(item, CancellationToken.None);
for (const call of incomingCalls) {
bucket.push(new Call(
call.from,
call.fromRanges.map(range => ({ range, uri: call.from.uri })),
parent
model
));
}
}
}
export class IdentityProvider implements IIdentityProvider<Call> {
constructor(
......@@ -110,13 +77,14 @@ export class IdentityProvider implements IIdentityProvider<Call> {
) { }
getId(element: Call): { toString(): string; } {
return this.getDirection() + hash(element.item.uri.toString(), hash(JSON.stringify(element.item.range))).toString() + (element.parent ? this.getId(element.parent) : '');
return this.getDirection() + '->' + element.item.id;
}
}
class CallRenderingTemplate {
constructor(
readonly iconLabel: IconLabel
readonly icon: HTMLDivElement,
readonly label: IconLabel
) { }
}
......@@ -127,25 +95,24 @@ export class CallRenderer implements ITreeRenderer<Call, FuzzyScore, CallRenderi
templateId: string = CallRenderer.id;
renderTemplate(container: HTMLElement): CallRenderingTemplate {
const iconLabel = new IconLabel(container, { supportHighlights: true });
return new CallRenderingTemplate(iconLabel);
dom.addClass(container, 'callhierarchy-element');
let icon = document.createElement('div');
container.appendChild(icon);
const label = new IconLabel(container, { supportHighlights: true });
return new CallRenderingTemplate(icon, label);
}
renderElement(node: ITreeNode<Call, FuzzyScore>, _index: number, template: CallRenderingTemplate): void {
const { element, filterData } = node;
template.iconLabel.setLabel(
template.icon.className = SymbolKinds.toCssClassName(element.item.kind, true);
template.label.setLabel(
element.item.name,
element.item.detail,
{
labelEscapeNewLines: true,
matches: createMatches(filterData),
extraClasses: [SymbolKinds.toCssClassName(element.item.kind, true)]
}
{ labelEscapeNewLines: true, matches: createMatches(filterData) }
);
}
disposeTemplate(template: CallRenderingTemplate): void {
template.iconLabel.dispose();
template.label.dispose();
}
}
......
......@@ -38,3 +38,14 @@
.monaco-workbench .call-hierarchy .tree {
height: 100%;
}
.monaco-workbench .call-hierarchy .tree .callhierarchy-element {
display: flex;
flex: 1;
flex-flow: row nowrap;
align-items: center;
}
.monaco-workbench .call-hierarchy .tree .callhierarchy-element .monaco-icon-label {
padding-left: 4px;
}
......@@ -862,28 +862,31 @@ suite('ExtHostLanguageFeatureCommands', function () {
test('Call Hierarchy, back and forth', async function () {
disposables.push(extHost.registerCallHierarchyProvider(nullExtensionDescription, defaultSelector, new class implements vscode.CallHierarchyItemProvider {
provideCallHierarchyIncomingCalls(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult<vscode.CallHierarchyIncomingCall[]> {
prepareCallHierarchy(document: vscode.TextDocument) {
return new types.CallHierarchyItem(types.SymbolKind.Array, 'ROOT', '', document.uri, new types.Range(0, 0, 2, 0), new types.Range(0, 0, 2, 0));
}
provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): vscode.ProviderResult<vscode.CallHierarchyIncomingCall[]> {
return [
new types.CallHierarchyIncomingCall(new types.CallHierarchyItem(types.SymbolKind.Array, 'IN', '', document.uri, new types.Range(0, 0, 2, 0), new types.Range(0, 0, 2, 0)), [new types.Range(0, 0, 0, 0)]),
new types.CallHierarchyIncomingCall(new types.CallHierarchyItem(types.SymbolKind.Array, 'IN', '', item.uri, new types.Range(0, 0, 2, 0), new types.Range(0, 0, 2, 0)), [new types.Range(0, 0, 0, 0)]),
];
}
provideCallHierarchyOutgoingCalls(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult<vscode.CallHierarchyOutgoingCall[]> {
provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): vscode.ProviderResult<vscode.CallHierarchyOutgoingCall[]> {
return [
new types.CallHierarchyOutgoingCall(new types.CallHierarchyItem(types.SymbolKind.Array, 'OUT', '', document.uri, new types.Range(0, 0, 2, 0), new types.Range(0, 0, 2, 0)), [new types.Range(0, 0, 0, 0)]),
new types.CallHierarchyOutgoingCall(new types.CallHierarchyItem(types.SymbolKind.Array, 'OUT', '', item.uri, new types.Range(0, 0, 2, 0), new types.Range(0, 0, 2, 0)), [new types.Range(0, 0, 0, 0)]),
];
}
}));
await rpcProtocol.sync();
let incoming = await commands.executeCommand<vscode.CallHierarchyIncomingCall[]>('vscode.executeCallHierarchyProviderIncomingCalls', model.uri, new types.Position(0, 10));
assert.equal(incoming.length, 1);
assert.ok(incoming[0].from instanceof types.CallHierarchyItem);
assert.equal(incoming[0].from.name, 'IN');
// let incoming = await commands.executeCommand<vscode.CallHierarchyIncomingCall[]>('vscode.executeCallHierarchyProviderIncomingCalls', model.uri, new types.Position(0, 10));
// assert.equal(incoming.length, 1);
// assert.ok(incoming[0].from instanceof types.CallHierarchyItem);
// assert.equal(incoming[0].from.name, 'IN');
let outgoing = await commands.executeCommand<vscode.CallHierarchyOutgoingCall[]>('vscode.executeCallHierarchyProviderOutgoingCalls', model.uri, new types.Position(0, 10));
assert.equal(outgoing.length, 1);
assert.ok(outgoing[0].to instanceof types.CallHierarchyItem);
assert.equal(outgoing[0].to.name, 'OUT');
// let outgoing = await commands.executeCommand<vscode.CallHierarchyOutgoingCall[]>('vscode.executeCallHierarchyProviderOutgoingCalls', model.uri, new types.Position(0, 10));
// assert.equal(outgoing.length, 1);
// assert.ok(outgoing[0].to instanceof types.CallHierarchyItem);
// assert.equal(outgoing[0].to.name, 'OUT');
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册