diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index faed925d709..e266d88ca41 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -32,13 +32,14 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews); } - async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, canDragAndDrop: boolean }): Promise { + async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, dragAndDropMimeTypes: string[] }): Promise { this.logService.trace('MainThreadTreeViews#$registerTreeViewDataProvider', treeViewId, options); 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 dndController = (options.dragAndDropMimeTypes.length > 0) + ? new TreeViewDragAndDropController(treeViewId, options.dragAndDropMimeTypes, 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. @@ -167,6 +168,7 @@ type TreeItemHandle = string; class TreeViewDragAndDropController implements ITreeViewDragAndDropController { constructor(private readonly treeViewId: string, + readonly supportedMimeTypes: string[], private readonly _proxy: ExtHostTreeViewsShape) { } async onDrop(dataTransfer: ITreeDataTransfer, targetTreeItem: ITreeItem, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 43d43d5bce1..8d170f3ebee 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1301,6 +1301,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I TestTag: extHostTypes.TestTag, TestRunProfileKind: extHostTypes.TestRunProfileKind, TextSearchCompleteMessageType: TextSearchCompleteMessageType, + TreeDataTransfer: extHostTypes.TreeDataTransfer, + TreeDataTransferItem: extHostTypes.TreeDataTransferItem, CoveredCount: extHostTypes.CoveredCount, FileCoverage: extHostTypes.FileCoverage, StatementCoverage: extHostTypes.StatementCoverage, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f953182df55..d0a72e28b18 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -295,7 +295,7 @@ export interface MainThreadTextEditorsShape extends IDisposable { } export interface MainThreadTreeViewsShape extends IDisposable { - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, canDragAndDrop: boolean; }): Promise; + $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, dragAndDropMimeTypes: string[] }): Promise; $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem; }): Promise; $reveal(treeViewId: string, itemInfo: { item: ITreeItem, parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise; $setMessage(treeViewId: string, message: string): void; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 86ac1db54ea..efbc0999101 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -85,8 +85,8 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { if (!options || !options.treeDataProvider) { throw new Error('Options with treeDataProvider is mandatory'); } - const canDragAndDrop = options.dragAndDropController !== undefined; - const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, canDragAndDrop: canDragAndDrop }); + const dragAndDropMimeTypes = (options.dragAndDropController === undefined) ? [] : options.dragAndDropController.supportedMimeTypes; + const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dragAndDropMimeTypes }); const treeView = this.createExtHostTreeView(viewId, options, extension); return { get onDidCollapseElement() { return treeView.onDidCollapseElement; }, @@ -139,9 +139,9 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { if ((sourceViewId === destinationViewId) && sourceTreeItemHandles) { const additionalTransferItems = await treeView.onWillDrop(sourceTreeItemHandles); if (additionalTransferItems) { - additionalTransferItems.items.forEach((value, key) => { + additionalTransferItems.forEach((value, key) => { if (value) { - treeDataTransfer.items.set(key, value); + treeDataTransfer.set(key, value); } }); } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 4623fe4de8d..8c34ee60014 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2296,6 +2296,30 @@ export enum TreeItemCollapsibleState { Expanded = 2 } +@es5ClassCompat +export class TreeDataTransferItem { + async asString(): Promise { + return JSON.stringify(this._value); + } + + constructor(private readonly _value: any) { } +} + +@es5ClassCompat +export class TreeDataTransfer { + private readonly _items: Map = new Map(); + get(mimeType: string): T | undefined { + return this._items.get(mimeType); + } + set(mimeType: string, value: T): void { + this._items.set(mimeType, value); + } + forEach(callbackfn: (value: T, key: string) => void): void { + this._items.forEach(callbackfn); + } +} + + @es5ClassCompat export class ThemeIcon { diff --git a/src/vs/workbench/api/common/shared/treeDataTransfer.ts b/src/vs/workbench/api/common/shared/treeDataTransfer.ts index 123d05adeec..fb0f9acb536 100644 --- a/src/vs/workbench/api/common/shared/treeDataTransfer.ts +++ b/src/vs/workbench/api/common/shared/treeDataTransfer.ts @@ -16,11 +16,9 @@ export interface TreeDataTransferDTO { export namespace TreeDataTransferConverter { export function toITreeDataTransfer(value: TreeDataTransferDTO): ITreeDataTransfer { - const newDataTransfer: ITreeDataTransfer = { - items: new Map() - }; + const newDataTransfer: ITreeDataTransfer = new Map(); value.types.forEach((type, index) => { - newDataTransfer.items.set(type, { + newDataTransfer.set(type, { asString: async () => value.items[index].asString }); }); @@ -32,7 +30,7 @@ export namespace TreeDataTransferConverter { types: [], items: [] }; - const entries = Array.from(value.items.entries()); + const entries = Array.from(value.entries()); for (const entry of entries) { newDTO.types.push(entry[0]); newDTO.items.push({ diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index c40d64a1475..82cc5cb73c4 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -1215,16 +1215,18 @@ 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 { + private readonly treeMimeType: string; constructor( private readonly treeId: string, - @ILabelService private readonly labelService: ILabelService) { } + @ILabelService private readonly labelService: ILabelService) { + this.treeMimeType = `tree/${treeId.toLowerCase()}`; + } private dndController: ITreeViewDragAndDropController | undefined; set controller(controller: ITreeViewDragAndDropController | undefined) { @@ -1238,16 +1240,27 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { id: this.treeId, itemHandles: treeItemsData.map(item => item.handle) }; - originalEvent.dataTransfer.setData(TREE_DRAG_SOURCE_INFO_MIME_TYPE, + originalEvent.dataTransfer.setData(this.treeMimeType, JSON.stringify(sourceInfo)); } } onDragOver(data: IDragAndDropData, targetElement: ITreeItem, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction { - if (!this.dndController) { + const dndController = this.dndController; + if (!dndController || !originalEvent.dataTransfer) { return false; } - return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand: true }; + const dragContainersSupportedType = originalEvent.dataTransfer.types.some((value, index) => { + if (value === this.treeMimeType) { + return true; + } else { + return dndController.supportedMimeTypes.indexOf(value) >= 0; + } + }); + if (dragContainersSupportedType) { + return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand: true }; + } + return false; } getDragURI(element: ITreeItem): string | null { @@ -1272,9 +1285,7 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { if (!originalEvent.dataTransfer || !this.dndController || !targetNode) { return; } - const treeDataTransfer: ITreeDataTransfer = { - items: new Map() - }; + const treeDataTransfer: ITreeDataTransfer = new Map(); let stringCount = Array.from(originalEvent.dataTransfer.items).reduce((previous, current) => { if (current.kind === 'string') { return previous + 1; @@ -1284,25 +1295,34 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { let treeSourceInfo: TreeDragSourceInfo | undefined; await new Promise(resolve => { - if (!originalEvent.dataTransfer || !this.dndController || !targetNode) { + function decrementStringCount() { + stringCount--; + if (stringCount === 0) { + resolve(); + } + } + + const dndController = this.dndController; + if (!originalEvent.dataTransfer || !dndController || !targetNode) { return; } for (const dataItem of originalEvent.dataTransfer.items) { + const type = dataItem.type; if (dataItem.kind === 'string') { - const type = dataItem.type; - dataItem.getAsString(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(); - } - }); + if ((type === this.treeMimeType) || (dndController.supportedMimeTypes.indexOf(type) >= 0)) { + dataItem.getAsString(dataValue => { + if (type === this.treeMimeType) { + treeSourceInfo = JSON.parse(dataValue); + } else { + treeDataTransfer.set(type, { + asString: () => Promise.resolve(dataValue) + }); + } + decrementStringCount(); + }); + } else { + decrementStringCount(); + } } } }); diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 7f72eedc88b..df43db9d318 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -646,9 +646,7 @@ export interface ITreeDataTransferItem { asString(): Thenable; } -export interface ITreeDataTransfer { - items: Map; -} +export type ITreeDataTransfer = Map; export interface ITreeView extends IDisposable { @@ -839,6 +837,7 @@ export interface ITreeViewDataProvider { } export interface ITreeViewDragAndDropController { + readonly supportedMimeTypes: string[]; onDrop(elements: ITreeDataTransfer, target: ITreeItem, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise; } diff --git a/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts b/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts index a18928327b3..9f72491c290 100644 --- a/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts @@ -73,7 +73,7 @@ suite('MainThreadHostTreeView', function () { } drain(): any { return null; } }, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService()); - mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, canDragAndDrop: false }); + mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, dragAndDropMimeTypes: [] }); await testExtensionService.whenInstalledExtensionsRegistered(); }); diff --git a/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts b/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts index 293666e1296..81f74dce81a 100644 --- a/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts +++ b/src/vscode-dts/vscode.proposed.treeViewDragAndDrop.d.ts @@ -26,24 +26,25 @@ declare module 'vscode' { dragAndDropController?: DragAndDropController; } - export interface TreeDataTransferItem { + export class TreeDataTransferItem { asString(): Thenable; + + constructor(value: any); } - export interface TreeDataTransfer { + export class TreeDataTransfer { /** * A map containing a mapping of the mime type of the corresponding data. * 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 - forEach: (callbackfn: (value: TreeDataTransferItem, key: string) => void) => void; - }; + get(mimeType: string): T | undefined; + set(mimeType: string, value: T): void; + forEach(callbackfn: (value: T, key: string) => void): void; } export interface DragAndDropController extends Disposable { - readonly supportedTypes: string[]; + readonly supportedMimeTypes: string[]; /** * When the user drops an item from this DragAndDropController on **another tree item** in **the same tree**,