Path: blob/main/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IRange } from '../../../../editor/common/core/range.js';6import { SymbolKind, ProviderResult, SymbolTag } from '../../../../editor/common/languages.js';7import { ITextModel } from '../../../../editor/common/model.js';8import { CancellationToken } from '../../../../base/common/cancellation.js';9import { LanguageFeatureRegistry } from '../../../../editor/common/languageFeatureRegistry.js';10import { URI } from '../../../../base/common/uri.js';11import { IPosition, Position } from '../../../../editor/common/core/position.js';12import { isNonEmptyArray } from '../../../../base/common/arrays.js';13import { onUnexpectedExternalError } from '../../../../base/common/errors.js';14import { IDisposable, RefCountedDisposable } from '../../../../base/common/lifecycle.js';15import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';16import { assertType } from '../../../../base/common/types.js';17import { IModelService } from '../../../../editor/common/services/model.js';18import { ITextModelService } from '../../../../editor/common/services/resolverService.js';1920export const enum CallHierarchyDirection {21CallsTo = 'incomingCalls',22CallsFrom = 'outgoingCalls'23}2425export interface CallHierarchyItem {26_sessionId: string;27_itemId: string;28kind: SymbolKind;29name: string;30detail?: string;31uri: URI;32range: IRange;33selectionRange: IRange;34tags?: SymbolTag[];35}3637export interface IncomingCall {38from: CallHierarchyItem;39fromRanges: IRange[];40}4142export interface OutgoingCall {43fromRanges: IRange[];44to: CallHierarchyItem;45}4647export interface CallHierarchySession {48roots: CallHierarchyItem[];49dispose(): void;50}5152export interface CallHierarchyProvider {5354prepareCallHierarchy(document: ITextModel, position: IPosition, token: CancellationToken): ProviderResult<CallHierarchySession>;5556provideIncomingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<IncomingCall[]>;5758provideOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<OutgoingCall[]>;59}6061export const CallHierarchyProviderRegistry = new LanguageFeatureRegistry<CallHierarchyProvider>();626364export class CallHierarchyModel {6566static async create(model: ITextModel, position: IPosition, token: CancellationToken): Promise<CallHierarchyModel | undefined> {67const [provider] = CallHierarchyProviderRegistry.ordered(model);68if (!provider) {69return undefined;70}71const session = await provider.prepareCallHierarchy(model, position, token);72if (!session) {73return undefined;74}75return new CallHierarchyModel(session.roots.reduce((p, c) => p + c._sessionId, ''), provider, session.roots, new RefCountedDisposable(session));76}7778readonly root: CallHierarchyItem;7980private constructor(81readonly id: string,82readonly provider: CallHierarchyProvider,83readonly roots: CallHierarchyItem[],84readonly ref: RefCountedDisposable,85) {86this.root = roots[0];87}8889dispose(): void {90this.ref.release();91}9293fork(item: CallHierarchyItem): CallHierarchyModel {94const that = this;95return new class extends CallHierarchyModel {96constructor() {97super(that.id, that.provider, [item], that.ref.acquire());98}99};100}101102async resolveIncomingCalls(item: CallHierarchyItem, token: CancellationToken): Promise<IncomingCall[]> {103try {104const result = await this.provider.provideIncomingCalls(item, token);105if (isNonEmptyArray(result)) {106return result;107}108} catch (e) {109onUnexpectedExternalError(e);110}111return [];112}113114async resolveOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): Promise<OutgoingCall[]> {115try {116const result = await this.provider.provideOutgoingCalls(item, token);117if (isNonEmptyArray(result)) {118return result;119}120} catch (e) {121onUnexpectedExternalError(e);122}123return [];124}125}126127// --- API command support128129const _models = new Map<string, CallHierarchyModel>();130131CommandsRegistry.registerCommand('_executePrepareCallHierarchy', async (accessor, ...args) => {132const [resource, position] = args;133assertType(URI.isUri(resource));134assertType(Position.isIPosition(position));135136const modelService = accessor.get(IModelService);137let textModel = modelService.getModel(resource);138let textModelReference: IDisposable | undefined;139if (!textModel) {140const textModelService = accessor.get(ITextModelService);141const result = await textModelService.createModelReference(resource);142textModel = result.object.textEditorModel;143textModelReference = result;144}145146try {147const model = await CallHierarchyModel.create(textModel, position, CancellationToken.None);148if (!model) {149return [];150}151//152_models.set(model.id, model);153_models.forEach((value, key, map) => {154if (map.size > 10) {155value.dispose();156_models.delete(key);157}158});159return [model.root];160161} finally {162textModelReference?.dispose();163}164});165166function isCallHierarchyItemDto(obj: any): obj is CallHierarchyItem {167return true;168}169170CommandsRegistry.registerCommand('_executeProvideIncomingCalls', async (_accessor, ...args) => {171const [item] = args;172assertType(isCallHierarchyItemDto(item));173174// find model175const model = _models.get(item._sessionId);176if (!model) {177return [];178}179180return model.resolveIncomingCalls(item, CancellationToken.None);181});182183CommandsRegistry.registerCommand('_executeProvideOutgoingCalls', async (_accessor, ...args) => {184const [item] = args;185assertType(isCallHierarchyItemDto(item));186187// find model188const model = _models.get(item._sessionId);189if (!model) {190return [];191}192193return model.resolveOutgoingCalls(item, CancellationToken.None);194});195196197