From 39a406daa381ddeb57323088f920d88f8bc8f40b Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 24 Jun 2020 16:28:19 +0200 Subject: [PATCH] Add resolveTreeItem to custom tree Part of #100741 --- src/vs/vscode.proposed.d.ts | 5 ++ .../api/browser/mainThreadTreeViews.ts | 14 ++++-- .../workbench/api/common/extHost.protocol.ts | 2 + .../workbench/api/common/extHostTreeViews.ts | 39 +++++++++++++++ src/vs/workbench/common/views.ts | 50 +++++++++++++++++++ .../contrib/views/browser/treeView.ts | 24 +++++---- 6 files changed, 119 insertions(+), 15 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e0bf0a26ca6..07b2c186925 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1146,6 +1146,11 @@ declare module 'vscode' { } + // https://github.com/microsoft/vscode/issues/100741 + export interface TreeDataProvider { + resolveTreeItem?(element: T, item: TreeItem2): TreeItem2 | Thenable; + } + export class TreeItem2 extends TreeItem { /** * Label describing this item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri). diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 58796f182f6..dc4706f3e84 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -5,7 +5,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; -import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions } from 'vs/workbench/common/views'; +import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions, ResolvableTreeItem } from 'vs/workbench/common/views'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { distinct } from 'vs/base/common/arrays'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -163,11 +163,13 @@ type TreeItemHandle = string; class TreeViewDataProvider implements ITreeViewDataProvider { private readonly itemsMap: Map = new Map(); + private hasResolve: Promise; constructor(private readonly treeViewId: string, private readonly _proxy: ExtHostTreeViewsShape, private readonly notificationService: INotificationService ) { + this.hasResolve = this._proxy.$hasResolve(this.treeViewId); } getChildren(treeItem?: ITreeItem): Promise { @@ -214,12 +216,16 @@ class TreeViewDataProvider implements ITreeViewDataProvider { return this.itemsMap.size === 0; } - private postGetChildren(elements: ITreeItem[]): ITreeItem[] { - const result: ITreeItem[] = []; + private async postGetChildren(elements: ITreeItem[]): Promise { + const result: ResolvableTreeItem[] = []; + const hasResolve = await this.hasResolve; if (elements) { for (const element of elements) { + const resolvable = new ResolvableTreeItem(element, hasResolve ? () => { + return this._proxy.$resolve(this.treeViewId, element.handle); + } : undefined); this.itemsMap.set(element.handle, element); - result.push(element); + result.push(resolvable); } } return result; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ecc9065c054..bb9dc00598e 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -987,6 +987,8 @@ export interface ExtHostTreeViewsShape { $setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void; $setSelection(treeViewId: string, treeItemHandles: string[]): void; $setVisible(treeViewId: string, visible: boolean): void; + $hasResolve(treeViewId: string): Promise; + $resolve(treeViewId: string, treeItemHandle: string): Promise; } export interface ExtHostWorkspaceShape { diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 0ae79328fda..2b376fb1bfc 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -119,6 +119,22 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { return treeView.getChildren(treeItemHandle); } + async $hasResolve(treeViewId: string): Promise { + const treeView = this.treeViews.get(treeViewId); + if (!treeView) { + throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)); + } + return treeView.hasResolve; + } + + $resolve(treeViewId: string, treeItemHandle: string): Promise { + const treeView = this.treeViews.get(treeViewId); + if (!treeView) { + throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)); + } + return treeView.resolveTreeItem(treeItemHandle); + } + $setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void { const treeView = this.treeViews.get(treeViewId); if (!treeView) { @@ -160,6 +176,7 @@ type TreeData = { message: boolean, element: T | Root | false }; interface TreeNode extends IDisposable { item: ITreeItem; + extensionItem: vscode.TreeItem2; parent: TreeNode | Root; children?: TreeNode[]; } @@ -329,6 +346,27 @@ class ExtHostTreeView extends Disposable { } } + get hasResolve(): boolean { + return !!this.dataProvider.resolveTreeItem; + } + + async resolveTreeItem(treeItemHandle: string): Promise { + if (!this.dataProvider.resolveTreeItem) { + return; + } + const element = this.elements.get(treeItemHandle); + if (element) { + const node = this.nodes.get(element); + if (node) { + const resolve = await this.dataProvider.resolveTreeItem(element, node.extensionItem); + // Resolvable elements. Currently only tooltip. + node.item.tooltip = resolve.tooltip; + return node.item; + } + } + return; + } + private resolveUnknownParentChain(element: T): Promise { return this.resolveParent(element) .then((parent) => { @@ -521,6 +559,7 @@ class ExtHostTreeView extends Disposable { return { item, + extensionItem: extensionTreeItem, parent, children: undefined, dispose(): void { disposable.dispose(); } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 2ccb10ae224..6a22b6c2387 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -648,6 +648,56 @@ export interface ITreeItem { accessibilityInformation?: IAccessibilityInformation; } +export class ResolvableTreeItem implements ITreeItem { + handle: string; + parentHandle?: string; + collapsibleState: TreeItemCollapsibleState; + label?: ITreeItemLabel; + description?: string | boolean; + icon?: UriComponents; + iconDark?: UriComponents; + themeIcon?: ThemeIcon; + resourceUri?: UriComponents; + tooltip?: string | IMarkdownString; + contextValue?: string; + command?: Command; + children?: ITreeItem[]; + accessibilityInformation?: IAccessibilityInformation; + resolve: () => Promise; + private resolved: boolean = false; + private _hasResolve: boolean = false; + constructor(treeItem: ITreeItem, resolve?: (() => Promise)) { + this.handle = treeItem.handle; + this.parentHandle = treeItem.parentHandle; + this.collapsibleState = treeItem.collapsibleState; + this.label = treeItem.label; + this.description = treeItem.description; + this.icon = treeItem.icon; + this.iconDark = treeItem.iconDark; + this.themeIcon = treeItem.themeIcon; + this.resourceUri = treeItem.resourceUri; + this.tooltip = treeItem.tooltip; + this.contextValue = treeItem.contextValue; + this.command = treeItem.command; + this.children = treeItem.children; + this.accessibilityInformation = treeItem.accessibilityInformation; + this._hasResolve = !!resolve; + this.resolve = async () => { + if (resolve && !this.resolved) { + const resolvedItem = await resolve(); + if (resolvedItem) { + // Resolvable elements. Currently only tooltip. + this.tooltip = resolvedItem.tooltip; + } + } + this.resolved = true; + }; + } + get hasResolve(): boolean { + return this._hasResolve; + } +} + export interface ITreeViewDataProvider { readonly isTreeEmpty?: boolean; onDidChangeEmpty?: Event; diff --git a/src/vs/workbench/contrib/views/browser/treeView.ts b/src/vs/workbench/contrib/views/browser/treeView.ts index 12091f7c71e..a9626ba711c 100644 --- a/src/vs/workbench/contrib/views/browser/treeView.ts +++ b/src/vs/workbench/contrib/views/browser/treeView.ts @@ -13,7 +13,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IMenuService, MenuId, MenuItemAction, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, IViewDescriptorService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, IViewDescriptorService, ViewContainer, ViewContainerLocation, ResolvableTreeItem } from 'vs/workbench/common/views'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService } from 'vs/platform/progress/common/progress'; @@ -38,7 +38,6 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; class Root implements ITreeItem { @@ -727,7 +726,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer { @@ -749,7 +748,8 @@ class TreeRenderer extends Disposable implements ITreeRenderer this.setAlignment(templateData.container, node))); - this.setupHovers(node.tooltip, templateData.container, disposableStore); + this.setupHovers(node, templateData.container, disposableStore, label); } - private setupHovers(tooltip: string | IMarkdownString | undefined, htmlElement: HTMLElement, disposableStore: DisposableStore): void { - if (!tooltip || isString(tooltip)) { + private setupHovers(node: ITreeItem, htmlElement: HTMLElement, disposableStore: DisposableStore, label: string | undefined): void { + if ((node.tooltip && isString(node.tooltip)) || !(node instanceof ResolvableTreeItem) || !node.hasResolve) { return; } - const text: IMarkdownString = tooltip; + const resolvableNode: ResolvableTreeItem = node; const hoverService = this.hoverService; const hoverDelay = this.hoverDelay; function mouseOver(this: HTMLElement, e: MouseEvent): any { @@ -801,9 +801,11 @@ class TreeRenderer extends Disposable implements ITreeRenderer { - if (isHovering) { - hoverService.showHover({ text, target: this }); + setTimeout(async () => { + await resolvableNode.resolve(); + const tooltip = resolvableNode.tooltip ?? label; + if (isHovering && tooltip) { + hoverService.showHover({ text: isString(tooltip) ? { value: tooltip } : tooltip, target: this }); } this.removeEventListener(DOM.EventType.MOUSE_LEAVE, mouseLeave); }, hoverDelay); -- GitLab