Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable file DataTransfer on tree views #150328

Merged
merged 1 commit into from May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/vs/workbench/api/browser/mainThreadTreeViews.ts
Expand Up @@ -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<void> {
async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: string[]; dragMimeTypes: string[]; hasHandleDrag: boolean; hasHandleDrop: boolean; supportsFileDataTransfers: boolean }): Promise<void> {
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.
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Expand Up @@ -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<void>;
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: readonly string[]; dragMimeTypes: readonly string[]; hasHandleDrag: boolean; hasHandleDrop: boolean; supportsFileDataTransfers: boolean }): Promise<void>;
$refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise<void>;
$reveal(treeViewId: string, itemInfo: { item: ITreeItem; parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise<void>;
$setMessage(treeViewId: string, message: string): void;
Expand Down
5 changes: 3 additions & 2 deletions src/vs/workbench/api/common/extHostTreeViews.ts
Expand Up @@ -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;

Expand Down Expand Up @@ -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; },
Expand Down
Expand Up @@ -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();
});

Expand Down
64 changes: 26 additions & 38 deletions src/vs/workbench/browser/parts/views/treeView.ts
Expand Up @@ -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 {

Expand Down Expand Up @@ -1503,40 +1503,21 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
}
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<void>(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<void>(resolve =>
dataItem.getAsString(dataValue => {
if (convertedType === this.treeMimeType) {
treeSourceInfo = JSON.parse(dataValue);
Expand All @@ -1545,20 +1526,27 @@ export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
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) {
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/common/views.ts
Expand Up @@ -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<VSDataTransfer | undefined>;
handleDrop(elements: VSDataTransfer, target: ITreeItem | undefined, token: CancellationToken, operationUuid?: string, sourceTreeId?: string, sourceTreeItemHandles?: string[]): Promise<void>;
}
Expand Down