diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 1df23985f9cef..faed925d70946 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -169,8 +169,8 @@ class TreeViewDragAndDropController implements ITreeViewDragAndDropController { constructor(private readonly treeViewId: string, private readonly _proxy: ExtHostTreeViewsShape) { } - async onDrop(dataTransfer: ITreeDataTransfer, targetTreeItem: ITreeItem): Promise { - return this._proxy.$onDrop(this.treeViewId, await TreeDataTransferConverter.toTreeDataTransferDTO(dataTransfer), targetTreeItem.handle); + async onDrop(dataTransfer: ITreeDataTransfer, targetTreeItem: ITreeItem, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise { + return this._proxy.$onDrop(this.treeViewId, await TreeDataTransferConverter.toTreeDataTransferDTO(dataTransfer), targetTreeItem.handle, sourceTreeId, sourceTreeItemHandles); } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b8565df3724a7..f953182df5507 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1271,7 +1271,7 @@ export interface ExtHostDocumentsAndEditorsShape { export interface ExtHostTreeViewsShape { $getChildren(treeViewId: string, treeItemHandle?: string): Promise; - $onDrop(treeViewId: string, treeDataTransfer: TreeDataTransferDTO, newParentTreeItemHandle: string): Promise; + $onDrop(destinationViewId: string, treeDataTransfer: TreeDataTransferDTO, newParentTreeItemHandle: string, sourceViewId?: string, sourceTreeItemHandles?: 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 e4730e17821fe..f5e63bdc8cc0c 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol'; -import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions, TREE_ITEM_DATA_TRANSFER_TYPE } from 'vs/workbench/common/views'; +import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions } from 'vs/workbench/common/views'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { asPromise } from 'vs/base/common/async'; import { TreeItemCollapsibleState, ThemeIcon, MarkdownString as MarkdownStringType } from 'vs/workbench/api/common/extHostTypes'; @@ -129,22 +129,22 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { return treeView.getChildren(treeItemHandle); } - async $onDrop(treeViewId: string, treeDataTransferDTO: TreeDataTransferDTO, newParentItemHandle: string): Promise { - const treeView = this.treeViews.get(treeViewId); + async $onDrop(destinationViewId: string, treeDataTransferDTO: TreeDataTransferDTO, newParentItemHandle: string, sourceViewId?: string, sourceTreeItemHandles?: string[]): Promise { + const treeView = this.treeViews.get(destinationViewId); if (!treeView) { - return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId))); + return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', destinationViewId))); } const treeDataTransfer = TreeDataTransferConverter.toITreeDataTransfer(treeDataTransferDTO); - if (treeDataTransfer.items.has(TREE_ITEM_DATA_TRANSFER_TYPE)) { - const sourceHandles: string[] = JSON.parse(await treeDataTransfer.items.get(TREE_ITEM_DATA_TRANSFER_TYPE)!.asString()); - const sourceElements = sourceHandles.map(handle => treeView.getExtensionElement(handle)).filter(element => !!element); - if (sourceElements.length > 0) { - treeDataTransfer.items.set(TREE_ITEM_DATA_TRANSFER_TYPE, { - asString: async () => JSON.stringify(sourceElements) - }); - } else { - treeDataTransfer.items.delete(TREE_ITEM_DATA_TRANSFER_TYPE); + if ((sourceViewId === destinationViewId) && sourceTreeItemHandles) { + const additionalTransferItems = await treeView.onWillDrop(sourceTreeItemHandles); + if (additionalTransferItems) { + for (const key of additionalTransferItems.items.keys()) { + const item = additionalTransferItems.items.get(key); + if (item) { + treeDataTransfer.items.set(key, item); + } + } } } return treeView.onDrop(treeDataTransfer, newParentItemHandle); @@ -404,6 +404,21 @@ class ExtHostTreeView extends Disposable { } } + async onWillDrop(sourceTreeItemHandles: TreeItemHandle[]): Promise { + const extensionTreeItems: T[] = []; + for (const sourceHandle of sourceTreeItemHandles) { + const extensionItem = this.getExtensionElement(sourceHandle); + if (extensionItem) { + extensionTreeItems.push(extensionItem); + } + } + + if (!this.dndController?.onWillDrop || (extensionTreeItems.length === 0)) { + return; + } + return this.dndController.onWillDrop(extensionTreeItems); + } + async onDrop(treeDataTransfer: vscode.TreeDataTransfer, targetHandleOrNode: TreeItemHandle): Promise { const target = this.getExtensionElement(targetHandleOrNode); if (!target) { diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index e84fe69e9a1ce..c40d64a1475e4 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, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem, ITreeViewDragAndDropController, ITreeDataTransfer, TREE_ITEM_DATA_TRANSFER_TYPE } from 'vs/workbench/common/views'; +import { ITreeView, ITreeViewDescriptor, IViewsRegistry, Extensions, IViewDescriptorService, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeItemLabel, ViewContainer, ViewContainerLocation, ResolvableTreeItem, ITreeViewDragAndDropController, ITreeDataTransfer } 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'; @@ -216,7 +216,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { this.collapseAllToggleContext = this.collapseAllToggleContextKey.bindTo(contextKeyService); this.refreshContextKey = new RawContextKey(`treeView.${this.id}.enableRefresh`, false, localize('treeView.enableRefresh', "Whether the tree view with id {0} enables refresh.", this.id)); this.refreshContext = this.refreshContextKey.bindTo(contextKeyService); - this.treeViewDnd = this.instantiationService.createInstance(CustomTreeViewDragAndDrop); + this.treeViewDnd = this.instantiationService.createInstance(CustomTreeViewDragAndDrop, this.id); this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); @@ -1215,8 +1215,16 @@ export class TreeView extends AbstractTreeView { } } +const TREE_DRAG_SOURCE_INFO_MIME_TYPE = 'tree/internalsourceinfo'; +interface TreeDragSourceInfo { + id: string, + itemHandles: string[]; +} + export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { - constructor(@ILabelService private readonly labelService: ILabelService) { } + constructor( + private readonly treeId: string, + @ILabelService private readonly labelService: ILabelService) { } private dndController: ITreeViewDragAndDropController | undefined; set controller(controller: ITreeViewDragAndDropController | undefined) { @@ -1225,8 +1233,13 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { if (originalEvent.dataTransfer) { - originalEvent.dataTransfer.setData(TREE_ITEM_DATA_TRANSFER_TYPE, - JSON.stringify((data as ElementsDragAndDropData).getData().map(treeItem => treeItem.handle))); + const treeItemsData = (data as ElementsDragAndDropData).getData(); + const sourceInfo: TreeDragSourceInfo = { + id: this.treeId, + itemHandles: treeItemsData.map(item => item.handle) + }; + originalEvent.dataTransfer.setData(TREE_DRAG_SOURCE_INFO_MIME_TYPE, + JSON.stringify(sourceInfo)); } } @@ -1268,6 +1281,8 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { } return previous; }, 0); + + let treeSourceInfo: TreeDragSourceInfo | undefined; await new Promise(resolve => { if (!originalEvent.dataTransfer || !this.dndController || !targetNode) { return; @@ -1276,9 +1291,13 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { if (dataItem.kind === 'string') { const type = dataItem.type; dataItem.getAsString(dataValue => { - treeDataTransfer.items.set(type, { - asString: () => Promise.resolve(dataValue) - }); + if (type === TREE_DRAG_SOURCE_INFO_MIME_TYPE) { + treeSourceInfo = JSON.parse(dataValue); + } else { + treeDataTransfer.items.set(type, { + asString: () => Promise.resolve(dataValue) + }); + } stringCount--; if (stringCount === 0) { resolve(); @@ -1287,6 +1306,6 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { } } }); - return this.dndController.onDrop(treeDataTransfer, targetNode); + return this.dndController.onDrop(treeDataTransfer, targetNode, treeSourceInfo?.id, treeSourceInfo?.itemHandles); } } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index d837af0438bce..7f72eedc88b2a 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -838,9 +838,8 @@ export interface ITreeViewDataProvider { getChildren(element?: ITreeItem): Promise; } -export const TREE_ITEM_DATA_TRANSFER_TYPE = 'text/treeitems'; export interface ITreeViewDragAndDropController { - onDrop(elements: ITreeDataTransfer, target: ITreeItem): Promise; + onDrop(elements: ITreeDataTransfer, target: ITreeItem, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise; } export interface IEditableData { diff --git a/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts b/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts index 70fd7dc8b5aa5..b2ae05a46a2a1 100644 --- a/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts +++ b/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts @@ -33,32 +33,28 @@ declare module 'vscode' { export interface TreeDataTransfer { /** * A map containing a mapping of the mime type of the corresponding data. - * The type for tree elements is text/treeitem. - * For example, you can reconstruct the your tree elements: - * ```ts - * JSON.parse(await (items.get('text/treeitem')!.asString())) - * ``` + * Trees that support drag and drop can implement `DragAndDropController.onWillDrop` to add additional mime types + * when the drop occurs on an item in the same tree. */ - items: { get: (mimeType: string) => TreeDataTransferItem | undefined }; + items: { + get: (mimeType: string) => TreeDataTransferItem | undefined + keys: () => IterableIterator; + }; } export interface DragAndDropController extends Disposable { readonly supportedTypes: string[]; /** - * todo@API maybe - * * When the user drops an item from this DragAndDropController on **another tree item** in **the same tree**, * `onWillDrop` will be called with the dropped tree item. This is the DragAndDropController's opportunity to * package the data from the dropped tree item into whatever format they want the target tree item to receive. * * The returned `TreeDataTransfer` will be merged with the original`TreeDataTransfer` for the operation. * - * Note for implementation later: This means that the `text/treeItem` mime type will go away. - * * @param source */ - // onWillDrop?(source: T): Thenable; + onWillDrop(source: T[]): Thenable; /** * Extensions should fire `TreeDataProvider.onDidChangeTreeData` for any elements that need to be refreshed.