mirror of
https://github.com/coder/code-server.git
synced 2026-06-02 18:39:33 +00:00
Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { CallHierarchyProviderRegistry, CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/common/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';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { registerEditorContribution, EditorAction2 } from 'vs/editor/browser/editorExtensions';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IContextKeyService, RawContextKey, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { PeekContext } from 'vs/editor/contrib/peekView/peekView';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { registerIcon, Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
const _ctxHasCallHierarchyProvider = new RawContextKey<boolean>('editorHasCallHierarchyProvider', false);
|
||||
const _ctxCallHierarchyVisible = new RawContextKey<boolean>('callHierarchyVisible', false);
|
||||
const _ctxCallHierarchyDirection = new RawContextKey<string>('callHierarchyDirection', undefined);
|
||||
|
||||
function sanitizedDirection(candidate: string): CallHierarchyDirection {
|
||||
return candidate === CallHierarchyDirection.CallsFrom || candidate === CallHierarchyDirection.CallsTo
|
||||
? candidate
|
||||
: CallHierarchyDirection.CallsTo;
|
||||
}
|
||||
|
||||
class CallHierarchyController implements IEditorContribution {
|
||||
|
||||
static readonly Id = 'callHierarchy';
|
||||
|
||||
static get(editor: ICodeEditor): CallHierarchyController {
|
||||
return editor.getContribution<CallHierarchyController>(CallHierarchyController.Id);
|
||||
}
|
||||
|
||||
private static readonly _StorageDirection = 'callHierarchy/defaultDirection';
|
||||
|
||||
private readonly _ctxHasProvider: IContextKey<boolean>;
|
||||
private readonly _ctxIsVisible: IContextKey<boolean>;
|
||||
private readonly _ctxDirection: IContextKey<string>;
|
||||
private readonly _dispoables = new DisposableStore();
|
||||
private readonly _sessionDisposables = new DisposableStore();
|
||||
|
||||
private _widget?: CallHierarchyTreePeekWidget;
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IStorageService private readonly _storageService: IStorageService,
|
||||
@ICodeEditorService private readonly _editorService: ICodeEditorService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
this._ctxIsVisible = _ctxCallHierarchyVisible.bindTo(this._contextKeyService);
|
||||
this._ctxHasProvider = _ctxHasCallHierarchyProvider.bindTo(this._contextKeyService);
|
||||
this._ctxDirection = _ctxCallHierarchyDirection.bindTo(this._contextKeyService);
|
||||
this._dispoables.add(Event.any<any>(_editor.onDidChangeModel, _editor.onDidChangeModelLanguage, CallHierarchyProviderRegistry.onDidChange)(() => {
|
||||
this._ctxHasProvider.set(_editor.hasModel() && CallHierarchyProviderRegistry.has(_editor.getModel()));
|
||||
}));
|
||||
this._dispoables.add(this._sessionDisposables);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._ctxHasProvider.reset();
|
||||
this._ctxIsVisible.reset();
|
||||
this._dispoables.dispose();
|
||||
}
|
||||
|
||||
async startCallHierarchyFromEditor(): Promise<void> {
|
||||
this._sessionDisposables.clear();
|
||||
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const document = this._editor.getModel();
|
||||
const position = this._editor.getPosition();
|
||||
if (!CallHierarchyProviderRegistry.has(document)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cts = new CancellationTokenSource();
|
||||
const model = CallHierarchyModel.create(document, position, cts.token);
|
||||
const direction = sanitizedDirection(this._storageService.get(CallHierarchyController._StorageDirection, StorageScope.GLOBAL, CallHierarchyDirection.CallsTo));
|
||||
|
||||
this._showCallHierarchyWidget(position, direction, model, cts);
|
||||
}
|
||||
|
||||
async startCallHierarchyFromCallHierarchy(): Promise<void> {
|
||||
if (!this._widget) {
|
||||
return;
|
||||
}
|
||||
const model = this._widget.getModel();
|
||||
const call = this._widget.getFocused();
|
||||
if (!call || !model) {
|
||||
return;
|
||||
}
|
||||
const newEditor = await this._editorService.openCodeEditor({ resource: call.item.uri }, this._editor);
|
||||
if (!newEditor) {
|
||||
return;
|
||||
}
|
||||
const newModel = model.fork(call.item);
|
||||
this._sessionDisposables.clear();
|
||||
|
||||
CallHierarchyController.get(newEditor)._showCallHierarchyWidget(
|
||||
Range.lift(newModel.root.selectionRange).getStartPosition(),
|
||||
this._widget.direction,
|
||||
Promise.resolve(newModel),
|
||||
new CancellationTokenSource()
|
||||
);
|
||||
}
|
||||
|
||||
private _showCallHierarchyWidget(position: IPosition, direction: CallHierarchyDirection, model: Promise<CallHierarchyModel | undefined>, cts: CancellationTokenSource) {
|
||||
|
||||
this._ctxIsVisible.set(true);
|
||||
this._ctxDirection.set(direction);
|
||||
Event.any<any>(this._editor.onDidChangeModel, this._editor.onDidChangeModelLanguage)(this.endCallHierarchy, this, this._sessionDisposables);
|
||||
this._widget = this._instantiationService.createInstance(CallHierarchyTreePeekWidget, this._editor, position, direction);
|
||||
this._widget.showLoading();
|
||||
this._sessionDisposables.add(this._widget.onDidClose(() => {
|
||||
this.endCallHierarchy();
|
||||
this._storageService.store(CallHierarchyController._StorageDirection, this._widget!.direction, StorageScope.GLOBAL);
|
||||
}));
|
||||
this._sessionDisposables.add({ dispose() { cts.dispose(true); } });
|
||||
this._sessionDisposables.add(this._widget);
|
||||
|
||||
model.then(model => {
|
||||
if (cts.token.isCancellationRequested) {
|
||||
return; // nothing
|
||||
}
|
||||
if (model) {
|
||||
this._sessionDisposables.add(model);
|
||||
this._widget!.showModel(model);
|
||||
}
|
||||
else {
|
||||
this._widget!.showMessage(localize('no.item', "No results"));
|
||||
}
|
||||
}).catch(e => {
|
||||
this._widget!.showMessage(localize('error', "Failed to show call hierarchy"));
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
showOutgoingCalls(): void {
|
||||
this._widget?.updateDirection(CallHierarchyDirection.CallsFrom);
|
||||
this._ctxDirection.set(CallHierarchyDirection.CallsFrom);
|
||||
}
|
||||
|
||||
showIncomingCalls(): void {
|
||||
this._widget?.updateDirection(CallHierarchyDirection.CallsTo);
|
||||
this._ctxDirection.set(CallHierarchyDirection.CallsTo);
|
||||
}
|
||||
|
||||
endCallHierarchy(): void {
|
||||
this._sessionDisposables.clear();
|
||||
this._ctxIsVisible.set(false);
|
||||
this._editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(CallHierarchyController.Id, CallHierarchyController);
|
||||
|
||||
registerAction2(class extends EditorAction2 {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.showCallHierarchy',
|
||||
title: { value: localize('title', "Peek Call Hierarchy"), original: 'Peek Call Hierarchy' },
|
||||
menu: {
|
||||
id: MenuId.EditorContextPeek,
|
||||
group: 'navigation',
|
||||
order: 1000,
|
||||
when: ContextKeyExpr.and(
|
||||
_ctxHasCallHierarchyProvider,
|
||||
PeekContext.notInPeekEditor
|
||||
),
|
||||
},
|
||||
keybinding: {
|
||||
when: EditorContextKeys.editorTextFocus,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H
|
||||
},
|
||||
precondition: ContextKeyExpr.and(
|
||||
_ctxHasCallHierarchyProvider,
|
||||
PeekContext.notInPeekEditor
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
return CallHierarchyController.get(editor).startCallHierarchyFromEditor();
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends EditorAction2 {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.showIncomingCalls',
|
||||
title: { value: localize('title.incoming', "Show Incoming Calls"), original: 'Show Incoming Calls' },
|
||||
icon: registerIcon('callhierarchy-incoming', Codicon.callIncoming),
|
||||
precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsFrom)),
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H,
|
||||
},
|
||||
menu: {
|
||||
id: CallHierarchyTreePeekWidget.TitleMenu,
|
||||
when: _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsFrom),
|
||||
order: 1,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) {
|
||||
return CallHierarchyController.get(editor).showIncomingCalls();
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends EditorAction2 {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.showOutgoingCalls',
|
||||
title: { value: localize('title.outgoing', "Show Outgoing Calls"), original: 'Show Outgoing Calls' },
|
||||
icon: registerIcon('callhierarchy-outgoing', Codicon.callOutgoing),
|
||||
precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsTo)),
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H,
|
||||
},
|
||||
menu: {
|
||||
id: CallHierarchyTreePeekWidget.TitleMenu,
|
||||
when: _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsTo),
|
||||
order: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) {
|
||||
return CallHierarchyController.get(editor).showOutgoingCalls();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
registerAction2(class extends EditorAction2 {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.refocusCallHierarchy',
|
||||
title: { value: localize('title.refocus', "Refocus Call Hierarchy"), original: 'Refocus Call Hierarchy' },
|
||||
precondition: _ctxCallHierarchyVisible,
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.Shift + KeyCode.Enter
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
return CallHierarchyController.get(editor).startCallHierarchyFromCallHierarchy();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
registerAction2(class extends EditorAction2 {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.closeCallHierarchy',
|
||||
title: localize('close', 'Close'),
|
||||
icon: Codicon.close,
|
||||
precondition: ContextKeyExpr.and(
|
||||
_ctxCallHierarchyVisible,
|
||||
ContextKeyExpr.not('config.editor.stablePeek')
|
||||
),
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib + 10,
|
||||
primary: KeyCode.Escape
|
||||
},
|
||||
menu: {
|
||||
id: CallHierarchyTreePeekWidget.TitleMenu,
|
||||
order: 1000
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
return CallHierarchyController.get(editor).endCallHierarchy();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,480 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/callHierarchy';
|
||||
import * as peekView from 'vs/editor/contrib/peekView/peekView';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { CallHierarchyDirection, CallHierarchyModel } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
|
||||
import { WorkbenchAsyncDataTree, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService';
|
||||
import { FuzzyScore } from 'vs/base/common/filters';
|
||||
import * as callHTree from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree';
|
||||
import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { SplitView, Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane } from 'vs/editor/common/model';
|
||||
import { registerThemingParticipant, themeColorFromId, IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IAction } 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, ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { MenuId, IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
|
||||
const enum State {
|
||||
Loading = 'loading',
|
||||
Message = 'message',
|
||||
Data = 'data'
|
||||
}
|
||||
|
||||
class LayoutInfo {
|
||||
|
||||
static store(info: LayoutInfo, storageService: IStorageService): void {
|
||||
storageService.store('callHierarchyPeekLayout', JSON.stringify(info), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
static retrieve(storageService: IStorageService): LayoutInfo {
|
||||
const value = storageService.get('callHierarchyPeekLayout', StorageScope.GLOBAL, '{}');
|
||||
const defaultInfo: LayoutInfo = { ratio: 0.7, height: 17 };
|
||||
try {
|
||||
return { ...defaultInfo, ...JSON.parse(value) };
|
||||
} catch {
|
||||
return defaultInfo;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
public ratio: number,
|
||||
public height: number
|
||||
) { }
|
||||
}
|
||||
|
||||
class CallHierarchyTree extends WorkbenchAsyncDataTree<CallHierarchyModel, callHTree.Call, FuzzyScore>{ }
|
||||
|
||||
export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget {
|
||||
|
||||
static readonly TitleMenu = new MenuId('callhierarchy/title');
|
||||
|
||||
private _parent!: HTMLElement;
|
||||
private _message!: HTMLElement;
|
||||
private _splitView!: SplitView;
|
||||
private _tree!: CallHierarchyTree;
|
||||
private _treeViewStates = new Map<CallHierarchyDirection, IAsyncDataTreeViewState>();
|
||||
private _editor!: EmbeddedCodeEditorWidget;
|
||||
private _dim!: Dimension;
|
||||
private _layoutInfo!: LayoutInfo;
|
||||
|
||||
private readonly _previewDisposable = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
private readonly _where: IPosition,
|
||||
private _direction: CallHierarchyDirection,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@peekView.IPeekViewService private readonly _peekViewService: peekView.IPeekViewService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ITextModelService private readonly _textModelService: ITextModelService,
|
||||
@IStorageService private readonly _storageService: IStorageService,
|
||||
@IMenuService private readonly _menuService: IMenuService,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
super(editor, { showFrame: true, showArrow: true, isResizeable: true, isAccessible: true }, _instantiationService);
|
||||
this.create();
|
||||
this._peekViewService.addExclusiveWidget(editor, this);
|
||||
this._applyTheme(themeService.getColorTheme());
|
||||
this._disposables.add(themeService.onDidColorThemeChange(this._applyTheme, this));
|
||||
this._disposables.add(this._previewDisposable);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
LayoutInfo.store(this._layoutInfo, this._storageService);
|
||||
this._splitView.dispose();
|
||||
this._tree.dispose();
|
||||
this._editor.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
get direction(): CallHierarchyDirection {
|
||||
return this._direction;
|
||||
}
|
||||
|
||||
private _applyTheme(theme: IColorTheme) {
|
||||
const borderColor = theme.getColor(peekView.peekViewBorder) || Color.transparent;
|
||||
this.style({
|
||||
arrowColor: borderColor,
|
||||
frameColor: borderColor,
|
||||
headerBackgroundColor: theme.getColor(peekView.peekViewTitleBackground) || Color.transparent,
|
||||
primaryHeadingColor: theme.getColor(peekView.peekViewTitleForeground),
|
||||
secondaryHeadingColor: theme.getColor(peekView.peekViewTitleInfoForeground)
|
||||
});
|
||||
}
|
||||
|
||||
protected _fillHead(container: HTMLElement): void {
|
||||
super._fillHead(container, true);
|
||||
|
||||
const menu = this._menuService.createMenu(CallHierarchyTreePeekWidget.TitleMenu, this._contextKeyService);
|
||||
const updateToolbar = () => {
|
||||
const actions: IAction[] = [];
|
||||
createAndFillInActionBarActions(menu, undefined, actions);
|
||||
this._actionbarWidget!.clear();
|
||||
this._actionbarWidget!.push(actions, { label: false, icon: true });
|
||||
};
|
||||
this._disposables.add(menu);
|
||||
this._disposables.add(menu.onDidChange(updateToolbar));
|
||||
updateToolbar();
|
||||
}
|
||||
|
||||
protected _getActionBarOptions(): IActionBarOptions {
|
||||
return {
|
||||
...super._getActionBarOptions(),
|
||||
orientation: ActionsOrientation.HORIZONTAL
|
||||
};
|
||||
}
|
||||
|
||||
protected _fillBody(parent: HTMLElement): void {
|
||||
|
||||
this._layoutInfo = LayoutInfo.retrieve(this._storageService);
|
||||
this._dim = new Dimension(0, 0);
|
||||
|
||||
this._parent = parent;
|
||||
parent.classList.add('call-hierarchy');
|
||||
|
||||
const message = document.createElement('div');
|
||||
message.classList.add('message');
|
||||
parent.appendChild(message);
|
||||
this._message = message;
|
||||
this._message.tabIndex = 0;
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.classList.add('results');
|
||||
parent.appendChild(container);
|
||||
|
||||
this._splitView = new SplitView(container, { orientation: Orientation.HORIZONTAL });
|
||||
|
||||
// editor stuff
|
||||
const editorContainer = document.createElement('div');
|
||||
editorContainer.classList.add('editor');
|
||||
container.appendChild(editorContainer);
|
||||
let editorOptions: IEditorOptions = {
|
||||
scrollBeyondLastLine: false,
|
||||
scrollbar: {
|
||||
verticalScrollbarSize: 14,
|
||||
horizontal: 'auto',
|
||||
useShadows: true,
|
||||
verticalHasArrows: false,
|
||||
horizontalHasArrows: false,
|
||||
alwaysConsumeMouseWheel: false
|
||||
},
|
||||
overviewRulerLanes: 2,
|
||||
fixedOverflowWidgets: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
};
|
||||
this._editor = this._instantiationService.createInstance(
|
||||
EmbeddedCodeEditorWidget,
|
||||
editorContainer,
|
||||
editorOptions,
|
||||
this.editor
|
||||
);
|
||||
|
||||
// tree stuff
|
||||
const treeContainer = document.createElement('div');
|
||||
treeContainer.classList.add('tree');
|
||||
container.appendChild(treeContainer);
|
||||
const options: IWorkbenchAsyncDataTreeOptions<callHTree.Call, FuzzyScore> = {
|
||||
sorter: new callHTree.Sorter(),
|
||||
accessibilityProvider: new callHTree.AccessibilityProvider(() => this._direction),
|
||||
identityProvider: new callHTree.IdentityProvider(() => this._direction),
|
||||
expandOnlyOnTwistieClick: true,
|
||||
overrideStyles: {
|
||||
listBackground: peekView.peekViewResultsBackground
|
||||
}
|
||||
};
|
||||
this._tree = this._instantiationService.createInstance(
|
||||
CallHierarchyTree,
|
||||
'CallHierarchyPeek',
|
||||
treeContainer,
|
||||
new callHTree.VirtualDelegate(),
|
||||
[this._instantiationService.createInstance(callHTree.CallRenderer)],
|
||||
this._instantiationService.createInstance(callHTree.DataSource, () => this._direction),
|
||||
options
|
||||
);
|
||||
|
||||
// split stuff
|
||||
this._splitView.addView({
|
||||
onDidChange: Event.None,
|
||||
element: editorContainer,
|
||||
minimumSize: 200,
|
||||
maximumSize: Number.MAX_VALUE,
|
||||
layout: (width) => {
|
||||
if (this._dim.height) {
|
||||
this._editor.layout({ height: this._dim.height, width });
|
||||
}
|
||||
}
|
||||
}, Sizing.Distribute);
|
||||
|
||||
this._splitView.addView({
|
||||
onDidChange: Event.None,
|
||||
element: treeContainer,
|
||||
minimumSize: 100,
|
||||
maximumSize: Number.MAX_VALUE,
|
||||
layout: (width) => {
|
||||
if (this._dim.height) {
|
||||
this._tree.layout(this._dim.height, width);
|
||||
}
|
||||
}
|
||||
}, Sizing.Distribute);
|
||||
|
||||
this._disposables.add(this._splitView.onDidSashChange(() => {
|
||||
if (this._dim.width) {
|
||||
this._layoutInfo.ratio = this._splitView.getViewSize(0) / this._dim.width;
|
||||
}
|
||||
}));
|
||||
|
||||
// update editor
|
||||
this._disposables.add(this._tree.onDidChangeFocus(this._updatePreview, this));
|
||||
|
||||
this._disposables.add(this._editor.onMouseDown(e => {
|
||||
const { event, target } = e;
|
||||
if (event.detail !== 2) {
|
||||
return;
|
||||
}
|
||||
const [focus] = this._tree.getFocus();
|
||||
if (!focus) {
|
||||
return;
|
||||
}
|
||||
this.dispose();
|
||||
this._editorService.openEditor({
|
||||
resource: focus.item.uri,
|
||||
options: { selection: target.range! }
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
this._disposables.add(this._tree.onMouseDblClick(e => {
|
||||
if (e.target === TreeMouseEventTarget.Twistie) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.element) {
|
||||
this.dispose();
|
||||
this._editorService.openEditor({
|
||||
resource: e.element.item.uri,
|
||||
options: { selection: e.element.item.selectionRange }
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
this._disposables.add(this._tree.onDidChangeSelection(e => {
|
||||
const [element] = e.elements;
|
||||
// don't close on click
|
||||
if (element && e.browserEvent instanceof KeyboardEvent) {
|
||||
this.dispose();
|
||||
this._editorService.openEditor({
|
||||
resource: element.item.uri,
|
||||
options: { selection: element.item.selectionRange }
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private async _updatePreview() {
|
||||
const [element] = this._tree.getFocus();
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._previewDisposable.clear();
|
||||
|
||||
// update: editor and editor highlights
|
||||
const options: IModelDecorationOptions = {
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'call-decoration',
|
||||
overviewRuler: {
|
||||
color: themeColorFromId(peekView.peekViewEditorMatchHighlight),
|
||||
position: OverviewRulerLane.Center
|
||||
},
|
||||
};
|
||||
|
||||
let previewUri: URI;
|
||||
if (this._direction === CallHierarchyDirection.CallsFrom) {
|
||||
// outgoing calls: show caller and highlight focused calls
|
||||
previewUri = element.parent ? element.parent.item.uri : element.model.root.uri;
|
||||
|
||||
} else {
|
||||
// incoming calls: show caller and highlight focused calls
|
||||
previewUri = element.item.uri;
|
||||
}
|
||||
|
||||
const value = await this._textModelService.createModelReference(previewUri);
|
||||
this._editor.setModel(value.object.textEditorModel);
|
||||
|
||||
// set decorations for caller ranges (if in the same file)
|
||||
let decorations: IModelDeltaDecoration[] = [];
|
||||
let fullRange: IRange | undefined;
|
||||
let locations = element.locations;
|
||||
if (!locations) {
|
||||
locations = [{ uri: element.item.uri, range: element.item.selectionRange }];
|
||||
}
|
||||
for (const loc of locations) {
|
||||
if (loc.uri.toString() === previewUri.toString()) {
|
||||
decorations.push({ range: loc.range, options });
|
||||
fullRange = !fullRange ? loc.range : Range.plusRange(loc.range, fullRange);
|
||||
}
|
||||
}
|
||||
if (fullRange) {
|
||||
this._editor.revealRangeInCenter(fullRange, ScrollType.Immediate);
|
||||
const ids = this._editor.deltaDecorations([], decorations);
|
||||
this._previewDisposable.add(toDisposable(() => this._editor.deltaDecorations(ids, [])));
|
||||
}
|
||||
this._previewDisposable.add(value);
|
||||
|
||||
// update: title
|
||||
const title = this._direction === CallHierarchyDirection.CallsFrom
|
||||
? localize('callFrom', "Calls from '{0}'", element.model.root.name)
|
||||
: localize('callsTo', "Callers of '{0}'", element.model.root.name);
|
||||
this.setTitle(title);
|
||||
}
|
||||
|
||||
showLoading(): void {
|
||||
this._parent.dataset['state'] = State.Loading;
|
||||
this.setTitle(localize('title.loading', "Loading..."));
|
||||
this._show();
|
||||
}
|
||||
|
||||
showMessage(message: string): void {
|
||||
this._parent.dataset['state'] = State.Message;
|
||||
this.setTitle('');
|
||||
this.setMetaTitle('');
|
||||
this._message.innerText = message;
|
||||
this._show();
|
||||
this._message.focus();
|
||||
}
|
||||
|
||||
async showModel(model: CallHierarchyModel): Promise<void> {
|
||||
|
||||
this._show();
|
||||
const viewState = this._treeViewStates.get(this._direction);
|
||||
|
||||
await this._tree.setInput(model, viewState);
|
||||
|
||||
const root = <ITreeNode<callHTree.Call>>this._tree.getNode(model).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}'", model.root.name)
|
||||
: localize('empt.callsTo', "No callers of '{0}'", model.root.name));
|
||||
|
||||
} else {
|
||||
this._parent.dataset['state'] = State.Data;
|
||||
if (!viewState || this._tree.getFocus().length === 0) {
|
||||
this._tree.setFocus([root.children[0].element]);
|
||||
}
|
||||
this._tree.domFocus();
|
||||
this._updatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
getModel(): CallHierarchyModel | undefined {
|
||||
return this._tree.getInput();
|
||||
}
|
||||
|
||||
getFocused(): callHTree.Call | undefined {
|
||||
return this._tree.getFocus()[0];
|
||||
}
|
||||
|
||||
async updateDirection(newDirection: CallHierarchyDirection): Promise<void> {
|
||||
const model = this._tree.getInput();
|
||||
if (model && newDirection !== this._direction) {
|
||||
this._treeViewStates.set(this._direction, this._tree.getViewState());
|
||||
this._direction = newDirection;
|
||||
await this.showModel(model);
|
||||
}
|
||||
}
|
||||
|
||||
private _show() {
|
||||
if (!this._isShowing) {
|
||||
this.editor.revealLineInCenterIfOutsideViewport(this._where.lineNumber, ScrollType.Smooth);
|
||||
super.show(Range.fromPositions(this._where), this._layoutInfo.height);
|
||||
}
|
||||
}
|
||||
|
||||
protected _onWidth(width: number) {
|
||||
if (this._dim) {
|
||||
this._doLayoutBody(this._dim.height, width);
|
||||
}
|
||||
}
|
||||
|
||||
protected _doLayoutBody(height: number, width: number): void {
|
||||
if (this._dim.height !== height || this._dim.width !== width) {
|
||||
super._doLayoutBody(height, width);
|
||||
this._dim = new Dimension(width, height);
|
||||
this._layoutInfo.height = this._viewZone ? this._viewZone.heightInLines : this._layoutInfo.height;
|
||||
this._splitView.layout(width);
|
||||
this._splitView.resizeView(0, width * this._layoutInfo.ratio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const referenceHighlightColor = theme.getColor(peekView.peekViewEditorMatchHighlight);
|
||||
if (referenceHighlightColor) {
|
||||
collector.addRule(`.monaco-editor .call-hierarchy .call-decoration { background-color: ${referenceHighlightColor}; }`);
|
||||
}
|
||||
const referenceHighlightBorder = theme.getColor(peekView.peekViewEditorMatchHighlightBorder);
|
||||
if (referenceHighlightBorder) {
|
||||
collector.addRule(`.monaco-editor .call-hierarchy .call-decoration { border: 2px solid ${referenceHighlightBorder}; box-sizing: border-box; }`);
|
||||
}
|
||||
const resultsBackground = theme.getColor(peekView.peekViewResultsBackground);
|
||||
if (resultsBackground) {
|
||||
collector.addRule(`.monaco-editor .call-hierarchy .tree { background-color: ${resultsBackground}; }`);
|
||||
}
|
||||
const resultsMatchForeground = theme.getColor(peekView.peekViewResultsFileForeground);
|
||||
if (resultsMatchForeground) {
|
||||
collector.addRule(`.monaco-editor .call-hierarchy .tree { color: ${resultsMatchForeground}; }`);
|
||||
}
|
||||
const resultsSelectedBackground = theme.getColor(peekView.peekViewResultsSelectionBackground);
|
||||
if (resultsSelectedBackground) {
|
||||
collector.addRule(`.monaco-editor .call-hierarchy .tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { background-color: ${resultsSelectedBackground}; }`);
|
||||
}
|
||||
const resultsSelectedForeground = theme.getColor(peekView.peekViewResultsSelectionForeground);
|
||||
if (resultsSelectedForeground) {
|
||||
collector.addRule(`.monaco-editor .call-hierarchy .tree .monaco-list:focus .monaco-list-rows > .monaco-list-row.selected:not(.highlighted) { color: ${resultsSelectedForeground} !important; }`);
|
||||
}
|
||||
const editorBackground = theme.getColor(peekView.peekViewEditorBackground);
|
||||
if (editorBackground) {
|
||||
collector.addRule(
|
||||
`.monaco-editor .call-hierarchy .editor .monaco-editor .monaco-editor-background,` +
|
||||
`.monaco-editor .call-hierarchy .editor .monaco-editor .inputarea.ime-input {` +
|
||||
` background-color: ${editorBackground};` +
|
||||
`}`
|
||||
);
|
||||
}
|
||||
const editorGutterBackground = theme.getColor(peekView.peekViewEditorGutterBackground);
|
||||
if (editorGutterBackground) {
|
||||
collector.addRule(
|
||||
`.monaco-editor .call-hierarchy .editor .monaco-editor .margin {` +
|
||||
` background-color: ${editorGutterBackground};` +
|
||||
`}`
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,161 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeSorter } from 'vs/base/browser/ui/tree/tree';
|
||||
import { CallHierarchyItem, CallHierarchyDirection, CallHierarchyModel, } from 'vs/workbench/contrib/callHierarchy/common/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, SymbolTag } from 'vs/editor/common/modes';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export class Call {
|
||||
constructor(
|
||||
readonly item: CallHierarchyItem,
|
||||
readonly locations: Location[] | undefined,
|
||||
readonly model: CallHierarchyModel,
|
||||
readonly parent: Call | undefined
|
||||
) { }
|
||||
|
||||
static compare(a: Call, b: Call): number {
|
||||
let res = compare(a.item.uri.toString(), b.item.uri.toString());
|
||||
if (res === 0) {
|
||||
res = Range.compareRangesUsingStarts(a.item.range, b.item.range);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export class DataSource implements IAsyncDataSource<CallHierarchyModel, Call> {
|
||||
|
||||
constructor(
|
||||
public getDirection: () => CallHierarchyDirection,
|
||||
) { }
|
||||
|
||||
hasChildren(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getChildren(element: CallHierarchyModel | Call): Promise<Call[]> {
|
||||
if (element instanceof CallHierarchyModel) {
|
||||
return element.roots.map(root => new Call(root, undefined, element, undefined));
|
||||
}
|
||||
|
||||
const { model, item } = element;
|
||||
|
||||
if (this.getDirection() === CallHierarchyDirection.CallsFrom) {
|
||||
return (await model.resolveOutgoingCalls(item, CancellationToken.None)).map(call => {
|
||||
return new Call(
|
||||
call.to,
|
||||
call.fromRanges.map(range => ({ range, uri: item.uri })),
|
||||
model,
|
||||
element
|
||||
);
|
||||
});
|
||||
|
||||
} else {
|
||||
return (await model.resolveIncomingCalls(item, CancellationToken.None)).map(call => {
|
||||
return new Call(
|
||||
call.from,
|
||||
call.fromRanges.map(range => ({ range, uri: call.from.uri })),
|
||||
model,
|
||||
element
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Sorter implements ITreeSorter<Call> {
|
||||
|
||||
compare(element: Call, otherElement: Call): number {
|
||||
return Call.compare(element, otherElement);
|
||||
}
|
||||
}
|
||||
|
||||
export class IdentityProvider implements IIdentityProvider<Call> {
|
||||
|
||||
constructor(
|
||||
public getDirection: () => CallHierarchyDirection
|
||||
) { }
|
||||
|
||||
getId(element: Call): { toString(): string; } {
|
||||
let res = this.getDirection() + JSON.stringify(element.item.uri) + JSON.stringify(element.item.range);
|
||||
if (element.parent) {
|
||||
res += this.getId(element.parent);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
class CallRenderingTemplate {
|
||||
constructor(
|
||||
readonly icon: HTMLDivElement,
|
||||
readonly label: IconLabel
|
||||
) { }
|
||||
}
|
||||
|
||||
export class CallRenderer implements ITreeRenderer<Call, FuzzyScore, CallRenderingTemplate> {
|
||||
|
||||
static readonly id = 'CallRenderer';
|
||||
|
||||
templateId: string = CallRenderer.id;
|
||||
|
||||
renderTemplate(container: HTMLElement): CallRenderingTemplate {
|
||||
container.classList.add('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;
|
||||
const deprecated = element.item.tags?.includes(SymbolTag.Deprecated);
|
||||
template.icon.className = SymbolKinds.toCssClassName(element.item.kind, true);
|
||||
template.label.setLabel(
|
||||
element.item.name,
|
||||
element.item.detail,
|
||||
{ labelEscapeNewLines: true, matches: createMatches(filterData), strikethrough: deprecated }
|
||||
);
|
||||
}
|
||||
disposeTemplate(template: CallRenderingTemplate): void {
|
||||
template.label.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class VirtualDelegate implements IListVirtualDelegate<Call> {
|
||||
|
||||
getHeight(_element: Call): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
getTemplateId(_element: Call): string {
|
||||
return CallRenderer.id;
|
||||
}
|
||||
}
|
||||
|
||||
export class AccessibilityProvider implements IListAccessibilityProvider<Call> {
|
||||
|
||||
constructor(
|
||||
public getDirection: () => CallHierarchyDirection
|
||||
) { }
|
||||
|
||||
getWidgetAriaLabel(): string {
|
||||
return localize('tree.aria', "Call Hierarchy");
|
||||
}
|
||||
|
||||
getAriaLabel(element: Call): string | null {
|
||||
if (this.getDirection() === CallHierarchyDirection.CallsFrom) {
|
||||
return localize('from', "calls from {0}", element.item.name);
|
||||
} else {
|
||||
return localize('to', "callers of {0}", element.item.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .call-hierarchy .results,
|
||||
.monaco-workbench .call-hierarchy .message {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .call-hierarchy[data-state="data"] .results {
|
||||
display: inherit;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .call-hierarchy[data-state="message"] .message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .call-hierarchy .editor,
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { SymbolKind, ProviderResult, SymbolTag } from 'vs/editor/common/modes';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
|
||||
export const enum CallHierarchyDirection {
|
||||
CallsTo = 'incomingCalls',
|
||||
CallsFrom = 'outgoingCalls'
|
||||
}
|
||||
|
||||
export interface CallHierarchyItem {
|
||||
_sessionId: string;
|
||||
_itemId: string;
|
||||
kind: SymbolKind;
|
||||
name: string;
|
||||
detail?: string;
|
||||
uri: URI;
|
||||
range: IRange;
|
||||
selectionRange: IRange;
|
||||
tags?: SymbolTag[]
|
||||
}
|
||||
|
||||
export interface IncomingCall {
|
||||
from: CallHierarchyItem;
|
||||
fromRanges: IRange[];
|
||||
}
|
||||
|
||||
export interface OutgoingCall {
|
||||
fromRanges: IRange[];
|
||||
to: CallHierarchyItem;
|
||||
}
|
||||
|
||||
export interface CallHierarchySession {
|
||||
roots: CallHierarchyItem[];
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface CallHierarchyProvider {
|
||||
|
||||
prepareCallHierarchy(document: ITextModel, position: IPosition, token: CancellationToken): ProviderResult<CallHierarchySession>;
|
||||
|
||||
provideIncomingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<IncomingCall[]>;
|
||||
|
||||
provideOutgoingCalls(item: CallHierarchyItem, token: CancellationToken): ProviderResult<OutgoingCall[]>;
|
||||
}
|
||||
|
||||
export const CallHierarchyProviderRegistry = new LanguageFeatureRegistry<CallHierarchyProvider>();
|
||||
|
||||
|
||||
class RefCountedDisposabled {
|
||||
|
||||
constructor(
|
||||
private readonly _disposable: IDisposable,
|
||||
private _counter = 1
|
||||
) { }
|
||||
|
||||
acquire() {
|
||||
this._counter++;
|
||||
return this;
|
||||
}
|
||||
|
||||
release() {
|
||||
if (--this._counter === 0) {
|
||||
this._disposable.dispose();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return new CallHierarchyModel(session.roots.reduce((p, c) => p + c._sessionId, ''), provider, session.roots, new RefCountedDisposabled(session));
|
||||
}
|
||||
|
||||
readonly root: CallHierarchyItem;
|
||||
|
||||
private constructor(
|
||||
readonly id: string,
|
||||
readonly provider: CallHierarchyProvider,
|
||||
readonly roots: CallHierarchyItem[],
|
||||
readonly ref: RefCountedDisposabled,
|
||||
) {
|
||||
this.root = roots[0];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.ref.release();
|
||||
}
|
||||
|
||||
fork(item: CallHierarchyItem): CallHierarchyModel {
|
||||
const that = this;
|
||||
return new class extends CallHierarchyModel {
|
||||
constructor() {
|
||||
super(that.id, that.provider, [item], that.ref.acquire());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 [];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// --- API command support
|
||||
|
||||
const _models = new Map<string, CallHierarchyModel>();
|
||||
|
||||
CommandsRegistry.registerCommand('_executePrepareCallHierarchy', async (accessor, ...args) => {
|
||||
const [resource, position] = args;
|
||||
assertType(URI.isUri(resource));
|
||||
assertType(Position.isIPosition(position));
|
||||
|
||||
const modelService = accessor.get(IModelService);
|
||||
let textModel = modelService.getModel(resource);
|
||||
let textModelReference: IDisposable | undefined;
|
||||
if (!textModel) {
|
||||
const textModelService = accessor.get(ITextModelService);
|
||||
const result = await textModelService.createModelReference(resource);
|
||||
textModel = result.object.textEditorModel;
|
||||
textModelReference = result;
|
||||
}
|
||||
|
||||
try {
|
||||
const model = await CallHierarchyModel.create(textModel, position, CancellationToken.None);
|
||||
if (!model) {
|
||||
return [];
|
||||
}
|
||||
//
|
||||
_models.set(model.id, model);
|
||||
_models.forEach((value, key, map) => {
|
||||
if (map.size > 10) {
|
||||
value.dispose();
|
||||
_models.delete(key);
|
||||
}
|
||||
});
|
||||
return [model.root];
|
||||
|
||||
} finally {
|
||||
textModelReference?.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
function isCallHierarchyItemDto(obj: any): obj is CallHierarchyItem {
|
||||
return true;
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand('_executeProvideIncomingCalls', async (_accessor, ...args) => {
|
||||
const [item] = args;
|
||||
assertType(isCallHierarchyItemDto(item));
|
||||
|
||||
// find model
|
||||
const model = _models.get(item._sessionId);
|
||||
if (!model) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return model.resolveIncomingCalls(item, CancellationToken.None);
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand('_executeProvideOutgoingCalls', async (_accessor, ...args) => {
|
||||
const [item] = args;
|
||||
assertType(isCallHierarchyItemDto(item));
|
||||
|
||||
// find model
|
||||
const model = _models.get(item._sessionId);
|
||||
if (!model) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return model.resolveOutgoingCalls(item, CancellationToken.None);
|
||||
});
|
||||
Reference in New Issue
Block a user