Skip to content

Initial implementation of drag and drop api #122239

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

Merged
merged 14 commits into from
May 4, 2021
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,28 @@ declare module 'vscode' {
}
//#endregion

//#region Custom Tree View Drag and Drop https://github.com/microsoft/vscode/issues/32592
export interface TreeViewOptions<T> {
/**
* * Whether the tree supports drag and drop.
*/
canDragAndDrop?: boolean;
}

export interface TreeDataProvider<T> {
/**
* Optional method to reparent an `element`.
*
* **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.
*/
setParent?(elements: T[], targetElement: T): Thenable<void>;
}

//#endregion

//#region Task presentation group: https://github.com/microsoft/vscode/issues/47265
export interface TaskPresentationOptions {
/**
Expand Down
7 changes: 6 additions & 1 deletion src/vs/workbench/api/browser/mainThreadTreeViews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews);
}

async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): Promise<void> {
async $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, canDragAndDrop: boolean }): Promise<void> {
this.logService.trace('MainThreadTreeViews#$registerTreeViewDataProvider', treeViewId, options);

this.extensionService.whenInstalledExtensionsRegistered().then(() => {
Expand All @@ -43,6 +43,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
// Set all other properties first!
viewer.showCollapseAllAction = !!options.showCollapseAll;
viewer.canSelectMany = !!options.canSelectMany;
viewer.canDragAndDrop = !!options.canDragAndDrop;
viewer.dataProvider = dataProvider;
this.registerListeners(treeViewId, viewer);
this._proxy.$setVisible(treeViewId, viewer.visible);
Expand Down Expand Up @@ -183,6 +184,10 @@ class TreeViewDataProvider implements ITreeViewDataProvider {
}));
}

setParent(treeItem: ITreeItem[], targetTreeItem: ITreeItem): Promise<void> {
return this._proxy.$setParent(this.treeViewId, treeItem.map(item => item.handle), targetTreeItem.handle);
}

getItemsToRefresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): ITreeItem[] {
const itemsToRefresh: ITreeItem[] = [];
if (itemsToRefreshByHandle) {
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export interface MainThreadTextEditorsShape extends IDisposable {
}

export interface MainThreadTreeViewsShape extends IDisposable {
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean; }): Promise<void>;
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, canDragAndDrop: 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 Expand Up @@ -1229,6 +1229,7 @@ export interface ExtHostDocumentsAndEditorsShape {

export interface ExtHostTreeViewsShape {
$getChildren(treeViewId: string, treeItemHandle?: string): Promise<ITreeItem[]>;
$setParent(treeViewId: string, treeItemHandle: string[], newParentTreeItemHandle: string): Promise<void>;
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void;
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
$setVisible(treeViewId: string, visible: boolean): void;
Expand Down
19 changes: 18 additions & 1 deletion src/vs/workbench/api/common/extHostTreeViews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ 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 });
const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, canDragAndDrop: !!options.canDragAndDrop });
const treeView = this.createExtHostTreeView(viewId, options, extension);
return {
get onDidCollapseElement() { return treeView.onDidCollapseElement; },
Expand Down Expand Up @@ -127,6 +127,14 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
return treeView.getChildren(treeItemHandle);
}

$setParent(treeViewId: string, treeItemHandles: string[], newParentItemHandle: string): Promise<void> {
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);
}

async $hasResolve(treeViewId: string): Promise<boolean> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
Expand Down Expand Up @@ -369,6 +377,15 @@ class ExtHostTreeView<T> extends Disposable {
}
}

setParent(treeItemHandleOrNodes: TreeItemHandle[], newParentHandleOrNode: TreeItemHandle): Promise<void> {
const elements = <T[]>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));
}
return Promise.resolve(undefined);
}

get hasResolve(): boolean {
return !!this.dataProvider.resolveTreeItem;
}
Expand Down
62 changes: 61 additions & 1 deletion src/vs/workbench/browser/parts/views/treeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMat
import { isString } from 'vs/base/common/types';
import { ILabelService } from 'vs/platform/label/common/label';
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
Expand Down Expand Up @@ -87,6 +89,7 @@ export class TreeViewPane extends ViewPane {
if (options.titleDescription !== this.treeView.description) {
this.updateTitleDescription(this.treeView.description);
}

this.updateTreeVisibility();
}

Expand Down Expand Up @@ -156,6 +159,7 @@ 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;
Expand Down Expand Up @@ -277,6 +281,12 @@ export class TreeView extends Disposable implements ITreeView {
}
return children;
}

async setParent(nodes: ITreeItem[], parentNode: ITreeItem): Promise<void> {
if (dataProvider.setParent) {
await dataProvider.setParent(nodes, parentNode);
}
}
};
if (this._dataProvider.onDidChangeEmpty) {
this._register(this._dataProvider.onDidChangeEmpty(() => this._onDidChangeWelcomeState.fire()));
Expand Down Expand Up @@ -329,6 +339,14 @@ 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;
}
Expand Down Expand Up @@ -503,6 +521,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,
overrideStyles: {
listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND
}
Expand Down Expand Up @@ -795,6 +814,18 @@ class TreeDataSource implements IAsyncDataSource<ITreeItem, ITreeItem> {
}
return result;
}

async setParent(elements: ITreeItem[], newParentElement: ITreeItem): Promise<void> {
if (this.treeView.dataProvider && this.treeView.dataProvider.setParent) {
try {
await this.withProgress(this.treeView.dataProvider.setParent(elements, newParentElement));
} catch (e) {
if (!(<string>e.message).startsWith('Bad progress location:')) {
throw e;
}
}
}
}
}

// todo@jrieken,sandy make this proper and contributable from extensions
Expand Down Expand Up @@ -1183,3 +1214,32 @@ export class CustomTreeView extends TreeView {
}
}
}

export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
constructor(private dataSource: TreeDataSource, @ILabelService private readonly labelService: ILabelService) { }

onDragOver(data: IDragAndDropData, targetElement: ITreeItem, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand: true };
}

getDragURI(element: ITreeItem): string | null {
return element.resourceUri ? URI.revive(element.resourceUri).toString() : element.handle;
}

getDragLabel?(elements: ITreeItem[]): string | undefined {
if (elements.length > 1) {
return String(elements.length);
}
const element = elements[0];
return element.label ? element.label.label : (element.resourceUri ? this.labelService.getUriLabel(URI.revive(element.resourceUri)) : undefined);
}

async drop(data: IDragAndDropData, targetNode: ITreeItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): Promise<void> {
if (data instanceof ElementsDragAndDropData) {
const elements = data.elements;
if (targetNode) {
await this.dataSource.setParent(elements, targetNode);
}
}
}
}
4 changes: 3 additions & 1 deletion src/vs/workbench/common/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,8 @@ export interface ITreeView extends IDisposable {

canSelectMany: boolean;

canDragAndDrop: boolean;

message?: string;

title: string;
Expand Down Expand Up @@ -810,7 +812,7 @@ export interface ITreeViewDataProvider {
readonly isTreeEmpty?: boolean;
onDidChangeEmpty?: Event<void>;
getChildren(element?: ITreeItem): Promise<ITreeItem[]>;

setParent?(elements: ITreeItem[], newParent: ITreeItem): Promise<void>;
}

export interface IEditableData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ suite('MainThreadHostTreeView', function () {
}
drain(): any { return null; }
}, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService());
mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false });
mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, canDragAndDrop: false });
await testExtensionService.whenInstalledExtensionsRegistered();
});

Expand Down