提交 3a3b62c1 编写于 作者: J Johannes Rieken

show calls as columns

上级 b01e65cf
......@@ -17,6 +17,7 @@ export abstract class BreadcrumbsItem {
dispose(): void { }
abstract equals(other: BreadcrumbsItem): boolean;
abstract render(container: HTMLElement): void;
layout(height: number | undefined): void { }
}
export class SimpleBreadcrumbsItem extends BreadcrumbsItem {
......@@ -140,6 +141,8 @@ export class BreadcrumbsWidget {
this._domNode.style.width = `${dim.width}px`;
this._domNode.style.height = `${dim.height}px`;
disposables.push(this._updateScrollbar());
this._items.forEach(item => item.layout(this._dimension.height));
}));
return combinedDisposable(disposables);
}
......@@ -327,6 +330,7 @@ export class BreadcrumbsWidget {
dom.clearNode(container);
container.className = '';
item.render(container);
item.layout(this._dimension && this._dimension.height);
container.tabIndex = -1;
container.setAttribute('role', 'listitem');
dom.addClass(container, 'monaco-breadcrumb-item');
......
......@@ -117,6 +117,10 @@ export class SplitView extends Disposable {
private _onDidSashReset = this._register(new Emitter<number>());
readonly onDidSashReset = this._onDidSashReset.event;
get items(): ReadonlyArray<IView> {
return this.viewItems.map(item => item.view);
}
get length(): number {
return this.viewItems.length;
}
......
......@@ -480,7 +480,12 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
return this._proxy.$provideCallHierarchyItem(handle, document.uri, position, token);
},
resolveCallHierarchyItem: (item, direction, token) => {
return this._proxy.$resolveCallHierarchyItem(handle, item, direction, token);
return this._proxy.$resolveCallHierarchyItem(handle, item, direction, token).then(data => {
if (data) {
data.forEach(tuple => tuple[1] = tuple[1].map(l => MainThreadLanguageFeatures._reviveLocationDto(l)));
}
return data;
});
}
});
}
......
......@@ -9,10 +9,12 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService
import { CallHierarchyProviderRegistry, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CallHierarchyPeekWidget } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek';
import { CallHierarchyTreePeekWidget, CallHierarchyColumnPeekWidget } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek';
import { Range } from 'vs/editor/common/core/range';
import { Event } from 'vs/base/common/event';
const tree = false;
registerAction({
id: 'editor.showCallHierarchy',
title: {
......@@ -20,7 +22,7 @@ registerAction({
original: 'Show Call Hierarchy'
},
menu: {
menuId: MenuId.EditorContext
menuId: MenuId.CommandPalette
},
handler: async function (accessor) {
......@@ -45,22 +47,29 @@ registerAction({
return;
}
const widget = instaService.createInstance(CallHierarchyPeekWidget, editor, provider, CallHierarchyDirection.CallsTo, rootItem);
if (tree) {
const widget = instaService.createInstance(CallHierarchyTreePeekWidget, editor, provider, CallHierarchyDirection.CallsTo, rootItem);
const listener = Event.any<any>(editor.onDidChangeModel, editor.onDidChangeModelLanguage)(_ => widget.dispose());
widget.show(Range.fromPositions(editor.getPosition()));
widget.onDidClose(() => {
console.log('DONE');
listener.dispose();
});
widget.tree.onDidOpen(e => {
const [element] = e.elements;
if (element) {
console.log(element);
}
});
} else {
const widget = instaService.createInstance(CallHierarchyColumnPeekWidget, editor, provider, CallHierarchyDirection.CallsTo, rootItem);
const listener = Event.any<any>(editor.onDidChangeModel, editor.onDidChangeModelLanguage)(_ => widget.dispose());
widget.show(Range.fromPositions(editor.getPosition()));
widget.onDidClose(() => {
console.log('DONE');
listener.dispose();
});
}
}
});
/*---------------------------------------------------------------------------------------------
* 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-columns .split-view-view .column {
height: 100%;
padding: 0 2px;
}
.monaco-workbench .call-hierarchy-columns .split-view-view:not(:last-child) .column {
border-right: solid 1px rgba(128, 128, 128, 0.527);
}
.monaco-workbench .call-hierarchy-columns .monaco-list-row .monaco-icon-label.call {
background-color: rgba(162, 241, 193, 0.38);
font-weight: 400;
}
.monaco-workbench .call-hierarchy-columns .monaco-list-row.focused.selected .monaco-icon-label.call::after{
content: '▶'; /*todo@joh use octicon*/
opacity: 0.5;
}
.monaco-workbench .call-hierarchy-columns .monaco-list-row .monaco-icon-label.location {
margin-left: 12px;
}
.monaco-workbench .call-hierarchy-columns .monaco-list-row:not(.focused):not(.selected) .monaco-icon-label.location {
opacity: 0.7;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CallHierarchyProvider, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import * as callHierarchyTree from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree';
import { Location, symbolKindToCssClass } from 'vs/editor/common/modes';
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { IView, Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { Dimension, addClass } from 'vs/base/browser/dom';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { Disposable } from 'vs/base/common/lifecycle';
export type ListElement = callHierarchyTree.Call | Location;
class LocationTemplate {
label: IconLabel;
}
export class LocationRenderer implements IListRenderer<Location, LocationTemplate> {
static id = 'LocationRenderer';
templateId: string = LocationRenderer.id;
constructor(
@ITextModelService private readonly _textModelService: ITextModelService,
) { }
renderTemplate(container: HTMLElement): LocationTemplate {
const label = new IconLabel(container, { supportHighlights: true });
return { label };
}
renderElement(element: Location, _index: number, template: LocationTemplate): void {
this._textModelService.createModelReference(element.uri).then(reference => {
const model = reference.object.textEditorModel;
const text = model.getLineContent(element.range.startLineNumber);
const indent = model.getLineFirstNonWhitespaceColumn(element.range.startLineNumber) - 1;
const prefix = String(element.range.startLineNumber);
const shift = (1 + indent /*left*/) - (prefix.length + 2 /*right*/);
template.label.setLabel(`${prefix}: ${text.substr(indent)}`, undefined, {
matches: [{ start: element.range.startColumn - shift, end: element.range.endColumn - shift }],
extraClasses: ['location']
});
reference.dispose();
});
}
disposeTemplate(template: LocationTemplate): void {
template.label.dispose();
}
}
class CallRenderingTemplate {
iconLabel: IconLabel;
}
export class CallRenderer implements IListRenderer<callHierarchyTree.Call, CallRenderingTemplate> {
static id = 'CallRenderer';
templateId: string = CallRenderer.id;
renderTemplate(container: HTMLElement): CallRenderingTemplate {
const iconLabel = new IconLabel(container, { supportHighlights: true });
return { iconLabel };
}
renderElement(element: callHierarchyTree.Call, _index: number, template: CallRenderingTemplate): void {
template.iconLabel.setLabel(
element.item.name,
element.item.detail,
{
labelEscapeNewLines: true,
extraClasses: ['call', symbolKindToCssClass(element.item.kind, true)]
}
);
}
disposeTemplate(template: CallRenderingTemplate): void {
template.iconLabel.dispose();
}
}
export class Delegate implements IListVirtualDelegate<ListElement> {
getHeight(element: ListElement): number {
return 23;
}
getTemplateId(element: ListElement): string {
if (element instanceof callHierarchyTree.Call) {
return CallRenderer.id;
} else {
return LocationRenderer.id;
}
}
}
export class CallColumn extends Disposable implements IView {
private _list: WorkbenchList<ListElement>;
private _token: CancellationTokenSource;
readonly element: HTMLElement = document.createElement('div');
readonly minimumSize: number = 100;
readonly maximumSize: number = 1000;
private readonly _onDidChange = new Emitter<number | undefined>();
readonly onDidChange: Event<number | undefined> = this._onDidChange.event;
constructor(
readonly index: number,
readonly root: callHierarchyTree.Call,
private readonly _provider: CallHierarchyProvider,
private readonly _direction: CallHierarchyDirection,
private readonly _emitter: Emitter<{ column: CallColumn, element: ListElement }>,
private readonly _getDim: () => Dimension,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
super();
addClass(this.element, 'column');
this._list = this._register(<WorkbenchList<ListElement>>this._instantiationService.createInstance(
WorkbenchList,
this.element,
new Delegate(),
[
new CallRenderer(),
this._instantiationService.createInstance(LocationRenderer)
],
{}
));
this._list.onDidOpen(e => {
this._emitter.fire({ column: this, element: e.elements[0] });
});
this._token = this._register(new CancellationTokenSource());
Promise.resolve(this._provider.resolveCallHierarchyItem(this.root.item, this._direction, this._token.token)).then(calls => {
if (calls) {
const input: ListElement[] = [];
for (const [item, locations] of calls) {
input.push(new callHierarchyTree.Call(this._direction, item, locations));
input.push(...locations);
}
this._list.splice(0, this._list.length, input);
} else {
// show message
}
}).catch(err => {
console.error(err);
});
}
layout(size: number, orientation: Orientation): void {
const { height, width } = this._getDim();
this._list.layout(height, Math.min(size, Math.ceil(width / 6)));
}
focus(): void {
this._list.domFocus();
this._list.focusFirst();
}
}
export class LocationColumn extends Disposable implements IView {
readonly element: HTMLElement = document.createElement('div');
readonly minimumSize: number = 370;
readonly maximumSize: number = Number.MAX_VALUE;
private readonly _onDidChange = new Emitter<number | undefined>();
readonly onDidChange: Event<number | undefined> = this._onDidChange.event;
private _editor: EmbeddedCodeEditorWidget;
constructor(
private _location: Location,
private readonly _getDim: () => Dimension,
editor: ICodeEditor,
@ITextModelService private readonly _textModelService: ITextModelService,
) {
super();
addClass(this.element, 'column');
// todo@joh pretty random selection of options
let options: IEditorOptions = {
readOnly: true,
scrollBeyondLastLine: false,
lineNumbers: 'off',
scrollbar: {
verticalScrollbarSize: 14,
horizontal: 'auto',
useShadows: true,
verticalHasArrows: false,
horizontalHasArrows: false
},
overviewRulerLanes: 2,
fixedOverflowWidgets: true,
minimap: {
enabled: false
},
codeLens: false,
glyphMargin: false,
};
this._editor = editor.invokeWithinContext(accessor => {
return this._register(accessor.get(IInstantiationService).createInstance(EmbeddedCodeEditorWidget, this.element, options, editor));
});
this._textModelService.createModelReference(this._location.uri).then(reference => {
this._editor.setModel(reference.object.textEditorModel);
this._editor.revealRangeInCenter(this._location.range);
this._editor.setSelection(this._location.range);
this._register(reference);
});
}
layout(size: number) {
this._editor.layout({ height: this._getDim().height, width: size });
}
focus(): void {
this._editor.focus();
}
}
......@@ -3,22 +3,26 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./callHierarchy';
import { PeekViewWidget } from 'vs/editor/contrib/referenceSearch/peekViewWidget';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CallHierarchyItem, CallHierarchyProvider, CallHierarchyDirection } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { FuzzyScore } from 'vs/base/common/filters';
import * as callHierarchyTree from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree';
import * as callHTree from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyTree';
import { IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree';
import { localize } from 'vs/nls';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { IRange } from 'vs/editor/common/core/range';
import { SplitView, Orientation, Sizing } from 'vs/base/browser/ui/splitview/splitview';
import { Dimension, addClass } from 'vs/base/browser/dom';
import { CallColumn, ListElement, LocationColumn } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyList';
import { Emitter } from 'vs/base/common/event';
export class CallHierarchyTreePeekWidget extends PeekViewWidget {
export class CallHierarchyPeekWidget extends PeekViewWidget {
private _tree: WorkbenchAsyncDataTree<CallHierarchyItem, callHierarchyTree.Call, FuzzyScore>;
private _tree: WorkbenchAsyncDataTree<CallHierarchyItem, callHTree.Call, FuzzyScore>;
constructor(
editor: ICodeEditor,
......@@ -33,8 +37,9 @@ export class CallHierarchyPeekWidget extends PeekViewWidget {
protected _fillBody(container: HTMLElement): void {
const options: IAsyncDataTreeOptions<callHierarchyTree.Call, FuzzyScore> = {
identityProvider: new callHierarchyTree.IdentityProvider(),
const options: IAsyncDataTreeOptions<callHTree.Call, FuzzyScore> = {
identityProvider: new callHTree.IdentityProvider(),
ariaLabel: localize('tree.aria', "Call Hierarchy"),
expandOnlyOnTwistieClick: true,
};
......@@ -42,14 +47,14 @@ export class CallHierarchyPeekWidget extends PeekViewWidget {
this._tree = <any>this._instantiationService.createInstance(
WorkbenchAsyncDataTree,
container,
new callHierarchyTree.VirtualDelegate(),
[new callHierarchyTree.CallRenderer()],
new callHierarchyTree.SingleDirectionDataSource(this._provider, this._direction),
new callHTree.VirtualDelegate(),
[new callHTree.CallRenderer()],
new callHTree.SingleDirectionDataSource(this._provider, this._direction),
options
);
}
get tree(): WorkbenchAsyncDataTree<CallHierarchyItem, callHierarchyTree.Call, FuzzyScore> {
get tree(): WorkbenchAsyncDataTree<CallHierarchyItem, callHTree.Call, FuzzyScore> {
return this._tree;
}
......@@ -67,3 +72,100 @@ export class CallHierarchyPeekWidget extends PeekViewWidget {
this._tree.layout(height, width);
}
}
export class CallHierarchyColumnPeekWidget extends PeekViewWidget {
private readonly _emitter = new Emitter<{ column: CallColumn, element: ListElement }>();
private _splitView: SplitView;
private _dim: Dimension;
constructor(
editor: ICodeEditor,
private readonly _provider: CallHierarchyProvider,
private readonly _direction: CallHierarchyDirection,
private readonly _root: CallHierarchyItem,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
super(editor, { showFrame: true, showArrow: true, isResizeable: true, isAccessible: true });
this.create();
}
protected _fillBody(container: HTMLElement): void {
addClass(container, 'call-hierarchy-columns');
this._splitView = new SplitView(container, { orientation: Orientation.HORIZONTAL });
this._emitter.event(e => {
const { element, column } = e;
// remove old
while (column.index + 1 < this._splitView.length) {
this._splitView.removeView(this._splitView.length - 1);
}
const getDim = () => this._dim || { height: undefined, width: undefined };
// add new
let newColumn: CallColumn | LocationColumn;
if (element instanceof callHTree.Call) {
newColumn = this._instantiationService.createInstance(
CallColumn,
column.index + 1,
element,
this._provider,
this._direction,
this._emitter,
getDim
);
} else {
newColumn = this._instantiationService.createInstance(
LocationColumn,
element,
getDim,
this.editor
);
}
this._disposables.push(newColumn);
this._splitView.addView(newColumn, Sizing.Distribute);
setTimeout(() => newColumn.focus());
let parts = this._splitView.items.map(column => column instanceof CallColumn ? column.root.item.name : undefined).filter(e => Boolean(e));
this.setTitle(localize('title', "Call Hierarchy for '{0}'", parts.join(' > ')));
});
}
show(where: IRange) {
this.editor.revealRangeInCenterIfOutsideViewport(where, ScrollType.Smooth);
super.show(where, 16);
this.setTitle(localize('title', "Call Hierarchy for '{0}'", this._root.name));
// add root items...
const item = this._instantiationService.createInstance(
CallColumn,
0,
new callHTree.Call(this._direction, this._root, []),
this._provider,
this._direction,
this._emitter,
() => this._dim || { height: undefined, width: undefined }
);
this._disposables.push(item);
this._splitView.addView(item, item.minimumSize);
item.focus();
}
protected _onWidth(width: number) {
if (this._dim) {
this._doLayoutBody(this._dim.height, width);
}
}
protected _doLayoutBody(height: number, width: number): void {
super._doLayoutBody(height, width);
this._dim = { height, width };
this._splitView.layout(width);
}
}
......@@ -5,19 +5,18 @@
import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { CallHierarchyItem, CallHierarchyDirection, CallHierarchyProvider } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { IRange } from 'vs/editor/common/core/range';
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 { symbolKindToCssClass } from 'vs/editor/common/modes';
import { symbolKindToCssClass, Location } from 'vs/editor/common/modes';
import { localize } from 'vs/nls';
export class Call {
constructor(
readonly direction: CallHierarchyDirection,
readonly item: CallHierarchyItem,
readonly ranges: IRange[] | undefined
readonly locations: Location[] | undefined
) { }
}
......@@ -36,7 +35,7 @@ export class SingleDirectionDataSource implements IAsyncDataSource<CallHierarchy
if (element instanceof Call) {
const calls = await this.provider.resolveCallHierarchyItem(element.item, this.direction, CancellationToken.None);
return calls
? calls.map(([item, locations]) => new Call(this.direction, item, locations.map(l => l.range)))
? calls.map(([item, locations]) => new Call(this.direction, item, locations))
: [];
} else {
return [new Call(this.direction, element, undefined)];
......@@ -67,13 +66,13 @@ export class CallRenderer implements ITreeRenderer<Call, FuzzyScore, CallRenderi
renderElement(node: ITreeNode<Call, FuzzyScore>, _index: number, template: CallRenderingTemplate): void {
const { element, filterData } = node;
let detail: string | undefined;
if (!element.ranges) {
if (!element.locations) {
// root
detail = element.item.detail;
} else {
detail = element.ranges.length === 1
detail = element.locations.length === 1
? localize('label.1', "(1 usage)")
: localize('label.n', "({0} usages)", element.ranges.length);
: localize('label.n', "({0} usages)", element.locations.length);
}
template.iconLabel.setLabel(
element.item.name,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册