Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -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();
}
});

View File

@@ -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};` +
`}`
);
}
});

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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);
});