Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { IRange } from '../../../../editor/common/core/range.js';
7
import { SymbolKind, ProviderResult, SymbolTag } from '../../../../editor/common/languages.js';
8
import { ITextModel } from '../../../../editor/common/model.js';
9
import { CancellationToken } from '../../../../base/common/cancellation.js';
10
import { LanguageFeatureRegistry } from '../../../../editor/common/languageFeatureRegistry.js';
11
import { URI } from '../../../../base/common/uri.js';
12
import { IPosition, Position } from '../../../../editor/common/core/position.js';
13
import { isNonEmptyArray } from '../../../../base/common/arrays.js';
14
import { onUnexpectedExternalError } from '../../../../base/common/errors.js';
15
import { IDisposable, RefCountedDisposable } from '../../../../base/common/lifecycle.js';
16
import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
17
import { assertType } from '../../../../base/common/types.js';
18
import { IModelService } from '../../../../editor/common/services/model.js';
19
import { ITextModelService } from '../../../../editor/common/services/resolverService.js';
20
21
export const enum CallHierarchyDirection {
22
CallsTo = 'incomingCalls',
23
CallsFrom = 'outgoingCalls'
24
}
25
26
export interface CallHierarchyItem {
27
_sessionId: string;
28
_itemId: string;
29
kind: SymbolKind;
30
name: string;
31
detail?: string;
32
uri: URI;
33
range: IRange;
34
selectionRange: IRange;
35
tags?: SymbolTag[];
36
}
37
38
export interface IncomingCall {
39
from: CallHierarchyItem;
40
fromRanges: IRange[];
41
}
42
43
export interface OutgoingCall {
44
fromRanges: IRange[];
45
to: CallHierarchyItem;
46
}
47
48
export interface CallHierarchySession {
49
roots: CallHierarchyItem[];
50
dispose(): void;
51
}
52
53
export interface CallHierarchyProvider {
54
55
prepareCallHierarchy(document: ITextModel, position: IPosition, token: CancellationToken): ProviderResult<CallHierarchySession>;
56
57
provideIncomingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<IncomingCall[]>;
58
59
provideOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<OutgoingCall[]>;
60
}
61
62
export const CallHierarchyProviderRegistry = new LanguageFeatureRegistry<CallHierarchyProvider>();
63
64
65
export class CallHierarchyModel {
66
67
static async create(model: ITextModel, position: IPosition, token: CancellationToken): Promise<CallHierarchyModel | undefined> {
68
const [provider] = CallHierarchyProviderRegistry.ordered(model);
69
if (!provider) {
70
return undefined;
71
}
72
const session = await provider.prepareCallHierarchy(model, position, token);
73
if (!session) {
74
return undefined;
75
}
76
return new CallHierarchyModel(session.roots.reduce((p, c) => p + c._sessionId, ''), provider, session.roots, new RefCountedDisposable(session));
77
}
78
79
readonly root: CallHierarchyItem;
80
81
private constructor(
82
readonly id: string,
83
readonly provider: CallHierarchyProvider,
84
readonly roots: CallHierarchyItem[],
85
readonly ref: RefCountedDisposable,
86
) {
87
this.root = roots[0];
88
}
89
90
dispose(): void {
91
this.ref.release();
92
}
93
94
fork(item: CallHierarchyItem): CallHierarchyModel {
95
const that = this;
96
return new class extends CallHierarchyModel {
97
constructor() {
98
super(that.id, that.provider, [item], that.ref.acquire());
99
}
100
};
101
}
102
103
async resolveIncomingCalls(item: CallHierarchyItem, token: CancellationToken): Promise<IncomingCall[]> {
104
try {
105
const result = await this.provider.provideIncomingCalls(item, token);
106
if (isNonEmptyArray(result)) {
107
return result;
108
}
109
} catch (e) {
110
onUnexpectedExternalError(e);
111
}
112
return [];
113
}
114
115
async resolveOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): Promise<OutgoingCall[]> {
116
try {
117
const result = await this.provider.provideOutgoingCalls(item, token);
118
if (isNonEmptyArray(result)) {
119
return result;
120
}
121
} catch (e) {
122
onUnexpectedExternalError(e);
123
}
124
return [];
125
}
126
}
127
128
// --- API command support
129
130
const _models = new Map<string, CallHierarchyModel>();
131
132
CommandsRegistry.registerCommand('_executePrepareCallHierarchy', async (accessor, ...args) => {
133
const [resource, position] = args;
134
assertType(URI.isUri(resource));
135
assertType(Position.isIPosition(position));
136
137
const modelService = accessor.get(IModelService);
138
let textModel = modelService.getModel(resource);
139
let textModelReference: IDisposable | undefined;
140
if (!textModel) {
141
const textModelService = accessor.get(ITextModelService);
142
const result = await textModelService.createModelReference(resource);
143
textModel = result.object.textEditorModel;
144
textModelReference = result;
145
}
146
147
try {
148
const model = await CallHierarchyModel.create(textModel, position, CancellationToken.None);
149
if (!model) {
150
return [];
151
}
152
//
153
_models.set(model.id, model);
154
_models.forEach((value, key, map) => {
155
if (map.size > 10) {
156
value.dispose();
157
_models.delete(key);
158
}
159
});
160
return [model.root];
161
162
} finally {
163
textModelReference?.dispose();
164
}
165
});
166
167
function isCallHierarchyItemDto(obj: any): obj is CallHierarchyItem {
168
return true;
169
}
170
171
CommandsRegistry.registerCommand('_executeProvideIncomingCalls', async (_accessor, ...args) => {
172
const [item] = args;
173
assertType(isCallHierarchyItemDto(item));
174
175
// find model
176
const model = _models.get(item._sessionId);
177
if (!model) {
178
return [];
179
}
180
181
return model.resolveIncomingCalls(item, CancellationToken.None);
182
});
183
184
CommandsRegistry.registerCommand('_executeProvideOutgoingCalls', async (_accessor, ...args) => {
185
const [item] = args;
186
assertType(isCallHierarchyItemDto(item));
187
188
// find model
189
const model = _models.get(item._sessionId);
190
if (!model) {
191
return [];
192
}
193
194
return model.resolveOutgoingCalls(item, CancellationToken.None);
195
});
196
197