Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts
4780 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 { localize, localize2 } from '../../../../nls.js';
7
import { CallHierarchyProviderRegistry, CallHierarchyDirection, CallHierarchyModel } from '../common/callHierarchy.js';
8
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
9
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
10
import { CallHierarchyTreePeekWidget } from './callHierarchyPeek.js';
11
import { Event } from '../../../../base/common/event.js';
12
import { registerEditorContribution, EditorAction2, EditorContributionInstantiation } from '../../../../editor/browser/editorExtensions.js';
13
import { IEditorContribution } from '../../../../editor/common/editorCommon.js';
14
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
15
import { IContextKeyService, RawContextKey, IContextKey, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
16
import { DisposableStore } from '../../../../base/common/lifecycle.js';
17
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
18
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
19
import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js';
20
import { PeekContext } from '../../../../editor/contrib/peekView/browser/peekView.js';
21
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
22
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
23
import { Range } from '../../../../editor/common/core/range.js';
24
import { IPosition } from '../../../../editor/common/core/position.js';
25
import { MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
26
import { Codicon } from '../../../../base/common/codicons.js';
27
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
28
import { isCancellationError } from '../../../../base/common/errors.js';
29
30
const _ctxHasCallHierarchyProvider = new RawContextKey<boolean>('editorHasCallHierarchyProvider', false, localize('editorHasCallHierarchyProvider', 'Whether a call hierarchy provider is available'));
31
const _ctxCallHierarchyVisible = new RawContextKey<boolean>('callHierarchyVisible', false, localize('callHierarchyVisible', 'Whether call hierarchy peek is currently showing'));
32
const _ctxCallHierarchyDirection = new RawContextKey<string>('callHierarchyDirection', undefined, { type: 'string', description: localize('callHierarchyDirection', 'Whether call hierarchy shows incoming or outgoing calls') });
33
34
function sanitizedDirection(candidate: string): CallHierarchyDirection {
35
return candidate === CallHierarchyDirection.CallsFrom || candidate === CallHierarchyDirection.CallsTo
36
? candidate
37
: CallHierarchyDirection.CallsTo;
38
}
39
40
class CallHierarchyController implements IEditorContribution {
41
42
static readonly Id = 'callHierarchy';
43
44
static get(editor: ICodeEditor): CallHierarchyController | null {
45
return editor.getContribution<CallHierarchyController>(CallHierarchyController.Id);
46
}
47
48
private static readonly _StorageDirection = 'callHierarchy/defaultDirection';
49
50
private readonly _ctxHasProvider: IContextKey<boolean>;
51
private readonly _ctxIsVisible: IContextKey<boolean>;
52
private readonly _ctxDirection: IContextKey<string>;
53
private readonly _dispoables = new DisposableStore();
54
private readonly _sessionDisposables = new DisposableStore();
55
56
private _widget?: CallHierarchyTreePeekWidget;
57
58
constructor(
59
private readonly _editor: ICodeEditor,
60
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
61
@IStorageService private readonly _storageService: IStorageService,
62
@ICodeEditorService private readonly _editorService: ICodeEditorService,
63
@IInstantiationService private readonly _instantiationService: IInstantiationService,
64
) {
65
this._ctxIsVisible = _ctxCallHierarchyVisible.bindTo(this._contextKeyService);
66
this._ctxHasProvider = _ctxHasCallHierarchyProvider.bindTo(this._contextKeyService);
67
this._ctxDirection = _ctxCallHierarchyDirection.bindTo(this._contextKeyService);
68
this._dispoables.add(Event.any<unknown>(_editor.onDidChangeModel, _editor.onDidChangeModelLanguage, CallHierarchyProviderRegistry.onDidChange)(() => {
69
this._ctxHasProvider.set(_editor.hasModel() && CallHierarchyProviderRegistry.has(_editor.getModel()));
70
}));
71
this._dispoables.add(this._sessionDisposables);
72
}
73
74
dispose(): void {
75
this._ctxHasProvider.reset();
76
this._ctxIsVisible.reset();
77
this._dispoables.dispose();
78
}
79
80
async startCallHierarchyFromEditor(): Promise<void> {
81
this._sessionDisposables.clear();
82
83
if (!this._editor.hasModel()) {
84
return;
85
}
86
87
const document = this._editor.getModel();
88
const position = this._editor.getPosition();
89
if (!CallHierarchyProviderRegistry.has(document)) {
90
return;
91
}
92
93
const cts = new CancellationTokenSource();
94
const model = CallHierarchyModel.create(document, position, cts.token);
95
const direction = sanitizedDirection(this._storageService.get(CallHierarchyController._StorageDirection, StorageScope.PROFILE, CallHierarchyDirection.CallsTo));
96
97
this._showCallHierarchyWidget(position, direction, model, cts);
98
}
99
100
async startCallHierarchyFromCallHierarchy(): Promise<void> {
101
if (!this._widget) {
102
return;
103
}
104
const model = this._widget.getModel();
105
const call = this._widget.getFocused();
106
if (!call || !model) {
107
return;
108
}
109
const newEditor = await this._editorService.openCodeEditor({ resource: call.item.uri }, this._editor);
110
if (!newEditor) {
111
return;
112
}
113
const newModel = model.fork(call.item);
114
this._sessionDisposables.clear();
115
116
CallHierarchyController.get(newEditor)?._showCallHierarchyWidget(
117
Range.lift(newModel.root.selectionRange).getStartPosition(),
118
this._widget.direction,
119
Promise.resolve(newModel),
120
new CancellationTokenSource()
121
);
122
}
123
124
private _showCallHierarchyWidget(position: IPosition, direction: CallHierarchyDirection, model: Promise<CallHierarchyModel | undefined>, cts: CancellationTokenSource) {
125
126
this._ctxIsVisible.set(true);
127
this._ctxDirection.set(direction);
128
Event.any<unknown>(this._editor.onDidChangeModel, this._editor.onDidChangeModelLanguage)(this.endCallHierarchy, this, this._sessionDisposables);
129
this._widget = this._instantiationService.createInstance(CallHierarchyTreePeekWidget, this._editor, position, direction);
130
this._widget.showLoading();
131
this._sessionDisposables.add(this._widget.onDidClose(() => {
132
this.endCallHierarchy();
133
this._storageService.store(CallHierarchyController._StorageDirection, this._widget!.direction, StorageScope.PROFILE, StorageTarget.USER);
134
}));
135
this._sessionDisposables.add({ dispose() { cts.dispose(true); } });
136
this._sessionDisposables.add(this._widget);
137
138
model.then(model => {
139
if (cts.token.isCancellationRequested) {
140
return; // nothing
141
}
142
if (model) {
143
this._sessionDisposables.add(model);
144
this._widget!.showModel(model);
145
}
146
else {
147
this._widget!.showMessage(localize('no.item', "No results"));
148
}
149
}).catch(err => {
150
if (isCancellationError(err)) {
151
this.endCallHierarchy();
152
return;
153
}
154
this._widget!.showMessage(localize('error', "Failed to show call hierarchy"));
155
});
156
}
157
158
showOutgoingCalls(): void {
159
this._widget?.updateDirection(CallHierarchyDirection.CallsFrom);
160
this._ctxDirection.set(CallHierarchyDirection.CallsFrom);
161
}
162
163
showIncomingCalls(): void {
164
this._widget?.updateDirection(CallHierarchyDirection.CallsTo);
165
this._ctxDirection.set(CallHierarchyDirection.CallsTo);
166
}
167
168
endCallHierarchy(): void {
169
this._sessionDisposables.clear();
170
this._ctxIsVisible.set(false);
171
this._editor.focus();
172
}
173
}
174
175
registerEditorContribution(CallHierarchyController.Id, CallHierarchyController, EditorContributionInstantiation.Eager); // eager because it needs to define a context key
176
177
registerAction2(class PeekCallHierarchyAction extends EditorAction2 {
178
179
constructor() {
180
super({
181
id: 'editor.showCallHierarchy',
182
title: localize2('title', 'Peek Call Hierarchy'),
183
menu: {
184
id: MenuId.EditorContextPeek,
185
group: 'navigation',
186
order: 1000,
187
when: ContextKeyExpr.and(
188
_ctxHasCallHierarchyProvider,
189
PeekContext.notInPeekEditor,
190
EditorContextKeys.isInEmbeddedEditor.toNegated(),
191
),
192
},
193
keybinding: {
194
when: EditorContextKeys.editorTextFocus,
195
weight: KeybindingWeight.WorkbenchContrib,
196
primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KeyH
197
},
198
precondition: ContextKeyExpr.and(
199
_ctxHasCallHierarchyProvider,
200
PeekContext.notInPeekEditor
201
),
202
f1: true
203
});
204
}
205
206
async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
207
return CallHierarchyController.get(editor)?.startCallHierarchyFromEditor();
208
}
209
});
210
211
registerAction2(class extends EditorAction2 {
212
213
constructor() {
214
super({
215
id: 'editor.showIncomingCalls',
216
title: localize2('title.incoming', 'Show Incoming Calls'),
217
icon: registerIcon('callhierarchy-incoming', Codicon.callIncoming, localize('showIncomingCallsIcons', 'Icon for incoming calls in the call hierarchy view.')),
218
precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsFrom)),
219
keybinding: {
220
weight: KeybindingWeight.WorkbenchContrib,
221
primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KeyH,
222
},
223
menu: {
224
id: CallHierarchyTreePeekWidget.TitleMenu,
225
when: _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsFrom),
226
order: 1,
227
}
228
});
229
}
230
231
runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) {
232
return CallHierarchyController.get(editor)?.showIncomingCalls();
233
}
234
});
235
236
registerAction2(class extends EditorAction2 {
237
238
constructor() {
239
super({
240
id: 'editor.showOutgoingCalls',
241
title: localize2('title.outgoing', 'Show Outgoing Calls'),
242
icon: registerIcon('callhierarchy-outgoing', Codicon.callOutgoing, localize('showOutgoingCallsIcon', 'Icon for outgoing calls in the call hierarchy view.')),
243
precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsTo)),
244
keybinding: {
245
weight: KeybindingWeight.WorkbenchContrib,
246
primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KeyH,
247
},
248
menu: {
249
id: CallHierarchyTreePeekWidget.TitleMenu,
250
when: _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsTo),
251
order: 1
252
}
253
});
254
}
255
256
runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) {
257
return CallHierarchyController.get(editor)?.showOutgoingCalls();
258
}
259
});
260
261
262
registerAction2(class extends EditorAction2 {
263
264
constructor() {
265
super({
266
id: 'editor.refocusCallHierarchy',
267
title: localize2('title.refocus', 'Refocus Call Hierarchy'),
268
precondition: _ctxCallHierarchyVisible,
269
keybinding: {
270
weight: KeybindingWeight.WorkbenchContrib,
271
primary: KeyMod.Shift + KeyCode.Enter
272
}
273
});
274
}
275
276
async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
277
return CallHierarchyController.get(editor)?.startCallHierarchyFromCallHierarchy();
278
}
279
});
280
281
282
registerAction2(class extends EditorAction2 {
283
284
constructor() {
285
super({
286
id: 'editor.closeCallHierarchy',
287
title: localize('close', 'Close'),
288
icon: Codicon.close,
289
precondition: _ctxCallHierarchyVisible,
290
keybinding: {
291
weight: KeybindingWeight.WorkbenchContrib + 10,
292
primary: KeyCode.Escape,
293
when: ContextKeyExpr.not('config.editor.stablePeek')
294
},
295
menu: {
296
id: CallHierarchyTreePeekWidget.TitleMenu,
297
order: 1000
298
}
299
});
300
}
301
302
runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): void {
303
return CallHierarchyController.get(editor)?.endCallHierarchy();
304
}
305
});
306
307