From 25b2357c9b9aa46a17c5662d79ff56810986668b Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 21 Feb 2022 13:48:40 +0100 Subject: [PATCH] Add keybinding for custom tree hovers Fixes #141021 --- .../api/browser/viewsExtensionPoint.ts | 55 ++++++++++++++++++- .../workbench/api/common/extHostTreeViews.ts | 4 +- .../browser/parts/editor/editorDropTarget.ts | 5 +- .../workbench/browser/parts/views/treeView.ts | 13 ++++- .../views/browser/treeViewsService.ts | 13 +++++ ...gAndDropService.ts => treeViewsService.ts} | 34 +++++++++--- 6 files changed, 104 insertions(+), 20 deletions(-) create mode 100644 src/vs/workbench/services/views/browser/treeViewsService.ts rename src/vs/workbench/services/views/common/{treeViewsDragAndDropService.ts => treeViewsService.ts} (60%) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index e8ab7ef610b1c..f839ed68780c2 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -13,14 +13,14 @@ import { localize } from 'vs/nls'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { Extensions as ViewletExtensions, PaneCompositeRegistry } from 'vs/workbench/browser/panecomposite'; -import { CustomTreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; +import { CustomTreeView, RawCustomTreeViewContextKey, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Extensions as ViewContainerExtensions, ICustomTreeViewDescriptor, ICustomViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, ICustomTreeViewDescriptor, ICustomViewDescriptor, IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ResolvableTreeItem, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug'; import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files'; import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/browser/remoteExplorer'; @@ -29,6 +29,14 @@ import { WebviewViewPane } from 'vs/workbench/contrib/webviewView/browser/webvie import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; +import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -255,6 +263,47 @@ class ViewsExtensionHandler implements IWorkbenchContribution { this.viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); this.handleAndRegisterCustomViewContainers(); this.handleAndRegisterCustomViews(); + + let showTreeHoverCancellation = new CancellationTokenSource(); + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.showTreeHover', + handler: async (accessor: ServicesAccessor, ...args: any[]) => { + showTreeHoverCancellation.cancel(); + showTreeHoverCancellation = new CancellationTokenSource(); + const listService = accessor.get(IListService); + const treeViewsService = accessor.get(ITreeViewsService); + const hoverService = accessor.get(IHoverService); + const lastFocusedList = listService.lastFocusedList; + if (!(lastFocusedList instanceof AsyncDataTree)) { + return; + } + const focus = lastFocusedList.getFocus(); + if (!focus || (focus.length === 0)) { + return; + } + const treeItem = focus[0]; + + if (treeItem instanceof ResolvableTreeItem) { + await treeItem.resolve(showTreeHoverCancellation.token); + } + if (!treeItem.tooltip) { + return; + } + const element = treeViewsService.getRenderedTreeElement(treeItem); + if (!element) { + return; + } + hoverService.showHover({ + content: treeItem.tooltip, + target: element, + hoverPosition: HoverPosition.BELOW, + hideOnHover: false + }, true); + }, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyI), + when: ContextKeyExpr.and(RawCustomTreeViewContextKey, WorkbenchListFocusContextKey) + }); } private handleAndRegisterCustomViewContainers() { diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index c59dc8850668b..560af394957dc 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -23,7 +23,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Command } from 'vs/editor/common/languages'; import { TreeDataTransferConverter, TreeDataTransferDTO } from 'vs/workbench/api/common/shared/treeDataTransfer'; -import { ITreeViewsDragAndDropService, TreeViewsDragAndDropService } from 'vs/workbench/services/views/common/treeViewsDragAndDropService'; +import { ITreeViewsService, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService'; type TreeItemHandle = string; @@ -50,7 +50,7 @@ function toTreeItemLabel(label: any, extension: IExtensionDescription): ITreeIte export class ExtHostTreeViews implements ExtHostTreeViewsShape { private treeViews: Map> = new Map>(); - private treeDragAndDropService: ITreeViewsDragAndDropService = new TreeViewsDragAndDropService(); + private treeDragAndDropService: ITreeViewsService = new TreeviewsService(); constructor( private _proxy: MainThreadTreeViewsShape, diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 37ac0c7c64e82..84a5ffa2e13b1 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -21,8 +21,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { ITreeViewsDragAndDropService } from 'vs/workbench/services/views/common/treeViewsDragAndDropService'; -import { ITreeDataTransfer } from 'vs/workbench/common/views'; +import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; interface IDropOperation { splitDirection?: GroupDirection; @@ -51,7 +50,7 @@ class DropOverlay extends Themable { @IInstantiationService private instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @ITreeViewsDragAndDropService private readonly treeViewsDragAndDropService: ITreeViewsDragAndDropService + @ITreeViewsService private readonly treeViewsDragAndDropService: ITreeViewsService ) { super(themeService); diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index cd6cd4dc4f147..9b412e5c5030e 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -59,7 +59,7 @@ import { isCancellationError } from 'vs/base/common/errors'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { CodeDataTransfers, DraggedTreeItemsIdentifier, fillEditorsDragData, LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { Schemas } from 'vs/base/common/network'; -import { ITreeViewsDragAndDropService } from 'vs/workbench/services/views/common/treeViewsDragAndDropService'; +import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; import { generateUuid } from 'vs/base/common/uuid'; import { ILogService } from 'vs/platform/log/common/log'; import { Mimes } from 'vs/base/common/mime'; @@ -151,6 +151,8 @@ class Root implements ITreeItem { const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data."); +export const RawCustomTreeViewContextKey = new RawContextKey('customTreeView', false); + class Tree extends WorkbenchAsyncDataTree { } abstract class AbstractTreeView extends Disposable implements ITreeView { @@ -555,6 +557,8 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { renderer.actionRunner = actionRunner; this.tree.contextKeyService.createKey(this.id, true); + const customTreeKey = RawCustomTreeViewContextKey.bindTo(this.tree.contextKeyService); + customTreeKey.set(true); this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner))); this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements))); this._register(this.tree.onDidChangeCollapseState(e => { @@ -895,7 +899,8 @@ class TreeRenderer extends Disposable implements ITreeRenderer this.setAlignment(templateData.container, node))); + this.treeViewsService.addRenderedTreeItemElement(node, templateData.container); + disposableStore.add(toDisposable(() => this.treeViewsService.removeRenderedTreeItemElement(node))); } private setAlignment(container: HTMLElement, treeItem: ITreeItem) { @@ -1265,7 +1272,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { private readonly treeId: string, @ILabelService private readonly labelService: ILabelService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ITreeViewsDragAndDropService private readonly treeViewsDragAndDropService: ITreeViewsDragAndDropService, + @ITreeViewsService private readonly treeViewsDragAndDropService: ITreeViewsService, @ILogService private readonly logService: ILogService) { this.treeMimeType = `application/vnd.code.tree.${treeId.toLowerCase()}`; } diff --git a/src/vs/workbench/services/views/browser/treeViewsService.ts b/src/vs/workbench/services/views/browser/treeViewsService.ts new file mode 100644 index 0000000000000..7ca5d8fe69884 --- /dev/null +++ b/src/vs/workbench/services/views/browser/treeViewsService.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ITreeDataTransfer, ITreeItem } from 'vs/workbench/common/views'; +import { ITreeViewsService as ITreeViewsServiceCommon, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService'; + +export interface ITreeViewsService extends ITreeViewsServiceCommon { } +export const ITreeViewsService = createDecorator('treeViewsService'); +registerSingleton(ITreeViewsService, TreeviewsService); diff --git a/src/vs/workbench/services/views/common/treeViewsDragAndDropService.ts b/src/vs/workbench/services/views/common/treeViewsService.ts similarity index 60% rename from src/vs/workbench/services/views/common/treeViewsDragAndDropService.ts rename to src/vs/workbench/services/views/common/treeViewsService.ts index 4ed58cd6c64c2..5e141cddd4eef 100644 --- a/src/vs/workbench/services/views/common/treeViewsDragAndDropService.ts +++ b/src/vs/workbench/services/views/common/treeViewsService.ts @@ -3,21 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ITreeDataTransfer } from 'vs/workbench/common/views'; - -export const ITreeViewsDragAndDropService = createDecorator>('treeViewsDragAndDropService'); -export interface ITreeViewsDragAndDropService { +export interface ITreeViewsService { readonly _serviceBrand: undefined; removeDragOperationTransfer(uuid: string | undefined): Promise | undefined; addDragOperationTransfer(uuid: string, transferPromise: Promise): void; + + getRenderedTreeElement(node: U): V | undefined; + addRenderedTreeItemElement(node: U, element: V): void; + removeRenderedTreeItemElement(node: U): void; } -export class TreeViewsDragAndDropService implements ITreeViewsDragAndDropService { +export class TreeviewsService implements ITreeViewsService { _serviceBrand: undefined; private _dragOperations: Map> = new Map(); + private _renderedElements: Map = new Map(); removeDragOperationTransfer(uuid: string | undefined): Promise | undefined { if ((uuid && this._dragOperations.has(uuid))) { @@ -31,6 +31,22 @@ export class TreeViewsDragAndDropService implements ITreeViewsDragAndDropServ addDragOperationTransfer(uuid: string, transferPromise: Promise): void { this._dragOperations.set(uuid, transferPromise); } -} -registerSingleton(ITreeViewsDragAndDropService, TreeViewsDragAndDropService); + + getRenderedTreeElement(node: U): V | undefined { + if (this._renderedElements.has(node)) { + return this._renderedElements.get(node); + } + return undefined; + } + + addRenderedTreeItemElement(node: U, element: V): void { + this._renderedElements.set(node, element); + } + + removeRenderedTreeItemElement(node: U): void { + if (this._renderedElements.has(node)) { + this._renderedElements.delete(node); + } + } +}