diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e9cf16d44ef..a4d27f9b330 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -915,24 +915,18 @@ declare module 'vscode' { //#region Custom Tree View Drag and Drop https://github.com/microsoft/vscode/issues/32592 export interface TreeViewOptions { - /** - * * Whether the tree supports drag and drop. - */ - canDragAndDrop?: boolean; + dragAndDropController?: DragAndDropController; } - export interface TreeDataProvider { + export interface DragAndDropController extends Disposable { /** - * Optional method to reparent an `element`. + * Extensions should fire `TreeDataProvider.onDidChangeTreeData` for any elements that need to be refreshed. * - * **NOTE:** This method should be implemented if the tree supports drag and drop. - * - * @param elements The selected elements that will be reparented. - * @param targetElement The new parent of the elements. + * @param source + * @param target */ - setParent?(elements: T[], targetElement: T): Thenable; + onDrop(source: T[], target: T): Thenable; } - //#endregion //#region Task presentation group: https://github.com/microsoft/vscode/issues/47265 diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 219fa248b0c..c131a19a431 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, ResolvableTreeItem } from 'vs/workbench/common/views'; +import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions, ResolvableTreeItem, ITreeViewDragAndDropController } 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'; @@ -37,13 +37,14 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie this.extensionService.whenInstalledExtensionsRegistered().then(() => { const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService); this._dataProviders.set(treeViewId, dataProvider); + const dndController = options.canDragAndDrop ? new TreeViewDragAndDropController(treeViewId, this._proxy) : undefined; const viewer = this.getTreeView(treeViewId); if (viewer) { // Order is important here. The internal tree isn't created until the dataProvider is set. // Set all other properties first! viewer.showCollapseAllAction = !!options.showCollapseAll; viewer.canSelectMany = !!options.canSelectMany; - viewer.canDragAndDrop = !!options.canDragAndDrop; + viewer.dragAndDropController = dndController; viewer.dataProvider = dataProvider; this.registerListeners(treeViewId, viewer); this._proxy.$setVisible(treeViewId, viewer.visible); @@ -162,6 +163,16 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie type TreeItemHandle = string; +class TreeViewDragAndDropController implements ITreeViewDragAndDropController { + + constructor(private readonly treeViewId: string, + private readonly _proxy: ExtHostTreeViewsShape) { } + + onDrop(treeItem: ITreeItem[], targetTreeItem: ITreeItem): Promise { + return this._proxy.$onDrop(this.treeViewId, treeItem.map(item => item.handle), targetTreeItem.handle); + } +} + class TreeViewDataProvider implements ITreeViewDataProvider { private readonly itemsMap: Map = new Map(); @@ -184,10 +195,6 @@ class TreeViewDataProvider implements ITreeViewDataProvider { })); } - setParent(treeItem: ITreeItem[], targetTreeItem: ITreeItem): Promise { - return this._proxy.$setParent(this.treeViewId, treeItem.map(item => item.handle), targetTreeItem.handle); - } - getItemsToRefresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): ITreeItem[] { const itemsToRefresh: ITreeItem[] = []; if (itemsToRefreshByHandle) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 0bfb1b47d6e..42b5c7f9e95 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1217,7 +1217,7 @@ export interface ExtHostDocumentsAndEditorsShape { export interface ExtHostTreeViewsShape { $getChildren(treeViewId: string, treeItemHandle?: string): Promise; - $setParent(treeViewId: string, treeItemHandle: string[], newParentTreeItemHandle: string): Promise; + $onDrop(treeViewId: string, treeItemHandle: string[], newParentTreeItemHandle: string): Promise; $setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void; $setSelection(treeViewId: string, treeItemHandles: string[]): void; $setVisible(treeViewId: string, visible: boolean): void; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index eb94d4f861b..8b02f4ab8ea 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -84,7 +84,8 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { if (!options || !options.treeDataProvider) { throw new Error('Options with treeDataProvider is mandatory'); } - const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, canDragAndDrop: !!options.canDragAndDrop }); + const canDragAndDrop = options.dragAndDropController !== undefined; + const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, canDragAndDrop: canDragAndDrop }); const treeView = this.createExtHostTreeView(viewId, options, extension); return { get onDidCollapseElement() { return treeView.onDidCollapseElement; }, @@ -127,12 +128,12 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { return treeView.getChildren(treeItemHandle); } - $setParent(treeViewId: string, treeItemHandles: string[], newParentItemHandle: string): Promise { + $onDrop(treeViewId: string, treeItemHandles: string[], newParentItemHandle: string): Promise { const treeView = this.treeViews.get(treeViewId); if (!treeView) { return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId))); } - return treeView.setParent(treeItemHandles, newParentItemHandle); + return treeView.onDrop(treeItemHandles, newParentItemHandle); } async $hasResolve(treeViewId: string): Promise { @@ -204,6 +205,7 @@ class ExtHostTreeView extends Disposable { private static readonly ID_HANDLE_PREFIX = '1'; private readonly dataProvider: vscode.TreeDataProvider; + private readonly dndController: vscode.DragAndDropController | undefined; private roots: TreeNode[] | null = null; private elements: Map = new Map(); @@ -250,6 +252,7 @@ class ExtHostTreeView extends Disposable { } } this.dataProvider = options.treeDataProvider; + this.dndController = options.dragAndDropController; if (this.dataProvider.onDidChangeTreeData) { this._register(this.dataProvider.onDidChangeTreeData(element => this._onDidChangeData.fire({ message: false, element }))); } @@ -377,11 +380,11 @@ class ExtHostTreeView extends Disposable { } } - setParent(treeItemHandleOrNodes: TreeItemHandle[], newParentHandleOrNode: TreeItemHandle): Promise { + onDrop(treeItemHandleOrNodes: TreeItemHandle[], targetHandleOrNode: TreeItemHandle): Promise { const elements = treeItemHandleOrNodes.map(item => this.getExtensionElement(item)).filter(element => !isUndefinedOrNull(element)); - const newParentElement = this.getExtensionElement(newParentHandleOrNode); - if (this.dataProvider.setParent && elements && newParentElement) { - return asPromise(() => this.dataProvider.setParent!(elements, newParentElement)); + const target = this.getExtensionElement(targetHandleOrNode); + if (elements && target) { + return asPromise(() => this.dndController?.onDrop(elements, target)); } return Promise.resolve(undefined); } diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index e3dbbad6383..29cb607489a 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -10,7 +10,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { MenuId, IMenuService, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem } from 'vs/workbench/common/views'; +import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem, ITreeViewDragAndDropController } from 'vs/workbench/common/views'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IThemeService, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -159,7 +159,6 @@ export class TreeView extends Disposable implements ITreeView { private treeContainer!: HTMLElement; private _messageValue: string | undefined; private _canSelectMany: boolean = false; - private _canDragAndDrop = false; private messageElement!: HTMLDivElement; private tree: Tree | undefined; private treeLabels: ResourceLabels | undefined; @@ -241,6 +240,13 @@ export class TreeView extends Disposable implements ITreeView { get viewLocation(): ViewContainerLocation { return this.viewDescriptorService.getViewLocationById(this.id)!; } + private _dragAndDropController: ITreeViewDragAndDropController | undefined; + get dragAndDropController(): ITreeViewDragAndDropController | undefined { + return this._dragAndDropController; + } + set dragAndDropController(dnd: ITreeViewDragAndDropController | undefined) { + this._dragAndDropController = dnd; + } private _dataProvider: ITreeViewDataProvider | undefined; get dataProvider(): ITreeViewDataProvider | undefined { @@ -281,12 +287,6 @@ export class TreeView extends Disposable implements ITreeView { } return children; } - - async setParent(nodes: ITreeItem[], parentNode: ITreeItem): Promise { - if (dataProvider.setParent) { - await dataProvider.setParent(nodes, parentNode); - } - } }; if (this._dataProvider.onDidChangeEmpty) { this._register(this._dataProvider.onDidChangeEmpty(() => this._onDidChangeWelcomeState.fire())); @@ -339,14 +339,6 @@ export class TreeView extends Disposable implements ITreeView { this._canSelectMany = canSelectMany; } - get canDragAndDrop(): boolean { - return this._canDragAndDrop; - } - - set canDragAndDrop(canDragAndDrop: boolean) { - this._canDragAndDrop = canDragAndDrop; - } - get hasIconForParentNode(): boolean { return this._hasIconForParentNode; } @@ -521,7 +513,7 @@ export class TreeView extends Disposable implements ITreeView { return e.collapsibleState !== TreeItemCollapsibleState.Expanded; }, multipleSelectionSupport: this.canSelectMany, - dnd: this.canDragAndDrop ? this.instantiationService.createInstance(CustomTreeViewDragAndDrop, dataSource) : undefined, + dnd: this.dragAndDropController ? this.instantiationService.createInstance(CustomTreeViewDragAndDrop, this.dragAndDropController) : undefined, overrideStyles: { listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND } @@ -814,18 +806,6 @@ class TreeDataSource implements IAsyncDataSource { } return result; } - - async setParent(elements: ITreeItem[], newParentElement: ITreeItem): Promise { - if (this.treeView.dataProvider && this.treeView.dataProvider.setParent) { - try { - await this.withProgress(this.treeView.dataProvider.setParent(elements, newParentElement)); - } catch (e) { - if (!(e.message).startsWith('Bad progress location:')) { - throw e; - } - } - } - } } // todo@jrieken,sandy make this proper and contributable from extensions @@ -1216,7 +1196,7 @@ export class CustomTreeView extends TreeView { } export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { - constructor(private dataSource: TreeDataSource, @ILabelService private readonly labelService: ILabelService) { } + constructor(private dndController: ITreeViewDragAndDropController, @ILabelService private readonly labelService: ILabelService) { } onDragOver(data: IDragAndDropData, targetElement: ITreeItem, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction { return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand: true }; @@ -1238,7 +1218,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { if (data instanceof ElementsDragAndDropData) { const elements = data.elements; if (targetNode) { - await this.dataSource.setParent(elements, targetNode); + await this.dndController.onDrop(elements, targetNode); } } } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 484af52fd8f..8a3fee85b94 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -630,12 +630,12 @@ export interface ITreeView extends IDisposable { dataProvider: ITreeViewDataProvider | undefined; + dragAndDropController?: ITreeViewDragAndDropController; + showCollapseAllAction: boolean; canSelectMany: boolean; - canDragAndDrop: boolean; - message?: string; title: string; @@ -812,7 +812,10 @@ export interface ITreeViewDataProvider { readonly isTreeEmpty?: boolean; onDidChangeEmpty?: Event; getChildren(element?: ITreeItem): Promise; - setParent?(elements: ITreeItem[], newParent: ITreeItem): Promise; +} + +export interface ITreeViewDragAndDropController { + onDrop(elements: ITreeItem[], target: ITreeItem): Promise; } export interface IEditableData {