diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index f0923d95701..56133dc4b20 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -37,14 +37,14 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews); } - async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: string[]; dragMimeTypes: string[]; hasHandleDrag: boolean; hasHandleDrop: boolean }): Promise { + async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: string[]; dragMimeTypes: string[]; hasHandleDrag: boolean; hasHandleDrop: boolean; supportsFileDataTransfers: boolean }): 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.hasHandleDrag || options.hasHandleDrop) - ? new TreeViewDragAndDropController(treeViewId, options.dropMimeTypes, options.dragMimeTypes, options.hasHandleDrag, this._proxy) : undefined; + ? new TreeViewDragAndDropController(treeViewId, options.dropMimeTypes, options.dragMimeTypes, options.hasHandleDrag, options.supportsFileDataTransfers, 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. @@ -201,6 +201,7 @@ class TreeViewDragAndDropController implements ITreeViewDragAndDropController { readonly dropMimeTypes: string[], readonly dragMimeTypes: string[], readonly hasWillDrop: boolean, + readonly supportsFileDataTransfers: boolean, private readonly _proxy: ExtHostTreeViewsShape) { } async handleDrop(dataTransfer: VSDataTransfer, targetTreeItem: ITreeItem | undefined, token: CancellationToken, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c7ebcd110f8..8975b45a790 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -258,7 +258,7 @@ export interface MainThreadTextEditorsShape extends IDisposable { } export interface MainThreadTreeViewsShape extends IDisposable { - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: readonly string[]; dragMimeTypes: readonly string[]; hasHandleDrag: boolean; hasHandleDrop: boolean }): Promise; + $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: readonly string[]; dragMimeTypes: readonly string[]; hasHandleDrag: boolean; hasHandleDrop: boolean; supportsFileDataTransfers: boolean }): 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 fdbb535e422..8bdd508109c 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -24,7 +24,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Command } from 'vs/editor/common/languages'; import { ITreeViewsService, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService'; -import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; type TreeItemHandle = string; @@ -92,7 +92,8 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { const dragMimeTypes = options.dragAndDropController?.dragMimeTypes ?? []; const hasHandleDrag = !!options.dragAndDropController?.handleDrag; const hasHandleDrop = !!options.dragAndDropController?.handleDrop; - const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dropMimeTypes, dragMimeTypes, hasHandleDrag, hasHandleDrop }); + const supportsFileDataTransfers = isProposedApiEnabled(extension, 'dataTransferFiles'); + const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dropMimeTypes, dragMimeTypes, hasHandleDrag, hasHandleDrop, supportsFileDataTransfers }); const treeView = this.createExtHostTreeView(viewId, options, extension); return { get onDidCollapseElement() { return treeView.onDidCollapseElement; }, diff --git a/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts b/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts index 1386a01b234..4afba6e05e6 100644 --- a/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts +++ b/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts @@ -75,7 +75,7 @@ suite('MainThreadHostTreeView', function () { } drain(): any { return null; } }, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService()); - mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, dropMimeTypes: [], dragMimeTypes: [], hasHandleDrag: false, hasHandleDrop: false }); + mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, dropMimeTypes: [], dragMimeTypes: [], hasHandleDrag: false, hasHandleDrop: false, supportsFileDataTransfers: false }); await testExtensionService.whenInstalledExtensionsRegistered(); }); diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 50da8ebb647..051926ebcb4 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -66,7 +66,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; import { ThemeSettings } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeViewsService } from 'vs/workbench/services/views/browser/treeViewsService'; -import { CodeDataTransfers } from 'vs/platform/dnd/browser/dnd'; +import { CodeDataTransfers, FileAdditionalNativeProperties } from 'vs/platform/dnd/browser/dnd'; export class TreeViewPane extends ViewPane { @@ -1503,40 +1503,21 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { } const treeDataTransfer = new VSDataTransfer(); const uris: URI[] = []; - let itemsCount = Array.from(originalEvent.dataTransfer.items).reduce((previous, current) => { - if ((current.kind === 'string') || (current.kind === 'file')) { - return previous + 1; - } - return previous; - }, 0); let treeSourceInfo: TreeDragSourceInfo | undefined; let willDropUuid: string | undefined; if (this.treeItemsTransfer.hasData(DraggedTreeItemsIdentifier.prototype)) { willDropUuid = this.treeItemsTransfer.getData(DraggedTreeItemsIdentifier.prototype)![0].identifier; } - await new Promise(resolve => { - function decrementStringCount() { - itemsCount--; - if (itemsCount === 0) { - // Check if there are uris to add and add them - if (uris.length) { - treeDataTransfer.setString(Mimes.uriList, uris.map(uri => uri.toString()).join('\n')); - } - resolve(); - } - } - if (!originalEvent.dataTransfer) { - return; - } - for (const dataItem of originalEvent.dataTransfer.items) { - const type = dataItem.type; - const kind = dataItem.kind; - const convertedType = this.convertKnownMimes(type, kind).type; - if ((INTERNAL_MIME_TYPES.indexOf(convertedType) < 0) - && (convertedType === this.treeMimeType) || (dndController.dropMimeTypes.indexOf(convertedType) >= 0)) { - if (dataItem.kind === 'string') { + await Promise.all([...originalEvent.dataTransfer.items].map(async dataItem => { + const type = dataItem.type; + const kind = dataItem.kind; + const convertedType = this.convertKnownMimes(type, kind).type; + if ((INTERNAL_MIME_TYPES.indexOf(convertedType) < 0) + && (convertedType === this.treeMimeType) || (dndController.dropMimeTypes.indexOf(convertedType) >= 0)) { + if (dataItem.kind === 'string') { + await new Promise(resolve => dataItem.getAsString(dataValue => { if (convertedType === this.treeMimeType) { treeSourceInfo = JSON.parse(dataValue); @@ -1545,20 +1526,27 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop { const converted = this.convertKnownMimes(type, kind, dataValue); treeDataTransfer.setString(converted.type, converted.value + ''); } - decrementStringCount(); - }); - } else if (dataItem.kind === 'file') { - const dataValue = dataItem.getAsFile(); - if (dataValue) { - uris.push(URI.file(dataValue.path)); + resolve(); + })); + } else if (dataItem.kind === 'file') { + const file = dataItem.getAsFile(); + if (file) { + uris.push(URI.file(file.path)); + const uri = (file as FileAdditionalNativeProperties).path ? URI.parse((file as FileAdditionalNativeProperties).path!) : undefined; + if (dndController.supportsFileDataTransfers) { + treeDataTransfer.setFile(type, file.name, uri, async () => { + return new Uint8Array(await file.arrayBuffer()); + }); } - decrementStringCount(); } - } else if (dataItem.kind === 'string' || dataItem.kind === 'file') { - decrementStringCount(); } } - }); + })); + + // Check if there are uris to add and add them + if (uris.length) { + treeDataTransfer.setString(Mimes.uriList, uris.map(uri => uri.toString()).join('\n')); + } const additionalWillDropPromise = this.treeViewsDragAndDropService.removeDragOperationTransfer(willDropUuid); if (!additionalWillDropPromise) { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index ac9b39c0c21..a255a45c0e2 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -831,6 +831,7 @@ export interface ITreeViewDataProvider { export interface ITreeViewDragAndDropController { readonly dropMimeTypes: string[]; readonly dragMimeTypes: string[]; + readonly supportsFileDataTransfers: boolean; handleDrag(sourceTreeItemHandles: string[], operationUuid: string, token: CancellationToken): Promise; handleDrop(elements: VSDataTransfer, target: ITreeItem | undefined, token: CancellationToken, operationUuid?: string, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise; }