Skip to content

Commit 39d180d

Browse files
Initial implementation of drag and drop api (#122239)
1 parent 2209664 commit 39d180d

File tree

7 files changed

+113
-6
lines changed

7 files changed

+113
-6
lines changed

src/vs/vscode.proposed.d.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,28 @@ declare module 'vscode' {
881881
}
882882
//#endregion
883883

884+
//#region Custom Tree View Drag and Drop https://github.com/microsoft/vscode/issues/32592
885+
export interface TreeViewOptions<T> {
886+
/**
887+
* * Whether the tree supports drag and drop.
888+
*/
889+
canDragAndDrop?: boolean;
890+
}
891+
892+
export interface TreeDataProvider<T> {
893+
/**
894+
* Optional method to reparent an `element`.
895+
*
896+
* **NOTE:** This method should be implemented if the tree supports drag and drop.
897+
*
898+
* @param elements The selected elements that will be reparented.
899+
* @param targetElement The new parent of the elements.
900+
*/
901+
setParent?(elements: T[], targetElement: T): Thenable<void>;
902+
}
903+
904+
//#endregion
905+
884906
//#region Task presentation group: https://github.com/microsoft/vscode/issues/47265
885907
export interface TaskPresentationOptions {
886908
/**

src/vs/workbench/api/browser/mainThreadTreeViews.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
3131
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews);
3232
}
3333

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

3737
this.extensionService.whenInstalledExtensionsRegistered().then(() => {
@@ -43,6 +43,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
4343
// Set all other properties first!
4444
viewer.showCollapseAllAction = !!options.showCollapseAll;
4545
viewer.canSelectMany = !!options.canSelectMany;
46+
viewer.canDragAndDrop = !!options.canDragAndDrop;
4647
viewer.dataProvider = dataProvider;
4748
this.registerListeners(treeViewId, viewer);
4849
this._proxy.$setVisible(treeViewId, viewer.visible);
@@ -183,6 +184,10 @@ class TreeViewDataProvider implements ITreeViewDataProvider {
183184
}));
184185
}
185186

187+
setParent(treeItem: ITreeItem[], targetTreeItem: ITreeItem): Promise<void> {
188+
return this._proxy.$setParent(this.treeViewId, treeItem.map(item => item.handle), targetTreeItem.handle);
189+
}
190+
186191
getItemsToRefresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): ITreeItem[] {
187192
const itemsToRefresh: ITreeItem[] = [];
188193
if (itemsToRefreshByHandle) {

src/vs/workbench/api/common/extHost.protocol.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ export interface MainThreadTextEditorsShape extends IDisposable {
289289
}
290290

291291
export interface MainThreadTreeViewsShape extends IDisposable {
292-
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean; }): Promise<void>;
292+
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean, canDragAndDrop: boolean; }): Promise<void>;
293293
$refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem; }): Promise<void>;
294294
$reveal(treeViewId: string, itemInfo: { item: ITreeItem, parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise<void>;
295295
$setMessage(treeViewId: string, message: string): void;
@@ -1229,6 +1229,7 @@ export interface ExtHostDocumentsAndEditorsShape {
12291229

12301230
export interface ExtHostTreeViewsShape {
12311231
$getChildren(treeViewId: string, treeItemHandle?: string): Promise<ITreeItem[]>;
1232+
$setParent(treeViewId: string, treeItemHandle: string[], newParentTreeItemHandle: string): Promise<void>;
12321233
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void;
12331234
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
12341235
$setVisible(treeViewId: string, visible: boolean): void;

src/vs/workbench/api/common/extHostTreeViews.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
8484
if (!options || !options.treeDataProvider) {
8585
throw new Error('Options with treeDataProvider is mandatory');
8686
}
87-
const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany });
87+
const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, canDragAndDrop: !!options.canDragAndDrop });
8888
const treeView = this.createExtHostTreeView(viewId, options, extension);
8989
return {
9090
get onDidCollapseElement() { return treeView.onDidCollapseElement; },
@@ -127,6 +127,14 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
127127
return treeView.getChildren(treeItemHandle);
128128
}
129129

130+
$setParent(treeViewId: string, treeItemHandles: string[], newParentItemHandle: string): Promise<void> {
131+
const treeView = this.treeViews.get(treeViewId);
132+
if (!treeView) {
133+
return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)));
134+
}
135+
return treeView.setParent(treeItemHandles, newParentItemHandle);
136+
}
137+
130138
async $hasResolve(treeViewId: string): Promise<boolean> {
131139
const treeView = this.treeViews.get(treeViewId);
132140
if (!treeView) {
@@ -369,6 +377,15 @@ class ExtHostTreeView<T> extends Disposable {
369377
}
370378
}
371379

380+
setParent(treeItemHandleOrNodes: TreeItemHandle[], newParentHandleOrNode: TreeItemHandle): Promise<void> {
381+
const elements = <T[]>treeItemHandleOrNodes.map(item => this.getExtensionElement(item)).filter(element => !isUndefinedOrNull(element));
382+
const newParentElement = this.getExtensionElement(newParentHandleOrNode);
383+
if (this.dataProvider.setParent && elements && newParentElement) {
384+
return asPromise(() => this.dataProvider.setParent!(elements, newParentElement));
385+
}
386+
return Promise.resolve(undefined);
387+
}
388+
372389
get hasResolve(): boolean {
373390
return !!this.dataProvider.resolveTreeItem;
374391
}

src/vs/workbench/browser/parts/views/treeView.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMat
3838
import { isString } from 'vs/base/common/types';
3939
import { ILabelService } from 'vs/platform/label/common/label';
4040
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
41-
import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
41+
import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree';
42+
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
43+
import { IDragAndDropData } from 'vs/base/browser/dnd';
4244
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
4345
import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults';
4446
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
@@ -87,6 +89,7 @@ export class TreeViewPane extends ViewPane {
8789
if (options.titleDescription !== this.treeView.description) {
8890
this.updateTitleDescription(this.treeView.description);
8991
}
92+
9093
this.updateTreeVisibility();
9194
}
9295

@@ -156,6 +159,7 @@ export class TreeView extends Disposable implements ITreeView {
156159
private treeContainer!: HTMLElement;
157160
private _messageValue: string | undefined;
158161
private _canSelectMany: boolean = false;
162+
private _canDragAndDrop = false;
159163
private messageElement!: HTMLDivElement;
160164
private tree: Tree | undefined;
161165
private treeLabels: ResourceLabels | undefined;
@@ -277,6 +281,12 @@ export class TreeView extends Disposable implements ITreeView {
277281
}
278282
return children;
279283
}
284+
285+
async setParent(nodes: ITreeItem[], parentNode: ITreeItem): Promise<void> {
286+
if (dataProvider.setParent) {
287+
await dataProvider.setParent(nodes, parentNode);
288+
}
289+
}
280290
};
281291
if (this._dataProvider.onDidChangeEmpty) {
282292
this._register(this._dataProvider.onDidChangeEmpty(() => this._onDidChangeWelcomeState.fire()));
@@ -329,6 +339,14 @@ export class TreeView extends Disposable implements ITreeView {
329339
this._canSelectMany = canSelectMany;
330340
}
331341

342+
get canDragAndDrop(): boolean {
343+
return this._canDragAndDrop;
344+
}
345+
346+
set canDragAndDrop(canDragAndDrop: boolean) {
347+
this._canDragAndDrop = canDragAndDrop;
348+
}
349+
332350
get hasIconForParentNode(): boolean {
333351
return this._hasIconForParentNode;
334352
}
@@ -503,6 +521,7 @@ export class TreeView extends Disposable implements ITreeView {
503521
return e.collapsibleState !== TreeItemCollapsibleState.Expanded;
504522
},
505523
multipleSelectionSupport: this.canSelectMany,
524+
dnd: this.canDragAndDrop ? this.instantiationService.createInstance(CustomTreeViewDragAndDrop, dataSource) : undefined,
506525
overrideStyles: {
507526
listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND
508527
}
@@ -795,6 +814,18 @@ class TreeDataSource implements IAsyncDataSource<ITreeItem, ITreeItem> {
795814
}
796815
return result;
797816
}
817+
818+
async setParent(elements: ITreeItem[], newParentElement: ITreeItem): Promise<void> {
819+
if (this.treeView.dataProvider && this.treeView.dataProvider.setParent) {
820+
try {
821+
await this.withProgress(this.treeView.dataProvider.setParent(elements, newParentElement));
822+
} catch (e) {
823+
if (!(<string>e.message).startsWith('Bad progress location:')) {
824+
throw e;
825+
}
826+
}
827+
}
828+
}
798829
}
799830

800831
// todo@jrieken,sandy make this proper and contributable from extensions
@@ -1183,3 +1214,32 @@ export class CustomTreeView extends TreeView {
11831214
}
11841215
}
11851216
}
1217+
1218+
export class CustomTreeViewDragAndDrop implements ITreeDragAndDrop<ITreeItem> {
1219+
constructor(private dataSource: TreeDataSource, @ILabelService private readonly labelService: ILabelService) { }
1220+
1221+
onDragOver(data: IDragAndDropData, targetElement: ITreeItem, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
1222+
return { accept: true, bubble: TreeDragOverBubble.Down, autoExpand: true };
1223+
}
1224+
1225+
getDragURI(element: ITreeItem): string | null {
1226+
return element.resourceUri ? URI.revive(element.resourceUri).toString() : element.handle;
1227+
}
1228+
1229+
getDragLabel?(elements: ITreeItem[]): string | undefined {
1230+
if (elements.length > 1) {
1231+
return String(elements.length);
1232+
}
1233+
const element = elements[0];
1234+
return element.label ? element.label.label : (element.resourceUri ? this.labelService.getUriLabel(URI.revive(element.resourceUri)) : undefined);
1235+
}
1236+
1237+
async drop(data: IDragAndDropData, targetNode: ITreeItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): Promise<void> {
1238+
if (data instanceof ElementsDragAndDropData) {
1239+
const elements = data.elements;
1240+
if (targetNode) {
1241+
await this.dataSource.setParent(elements, targetNode);
1242+
}
1243+
}
1244+
}
1245+
}

src/vs/workbench/common/views.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,8 @@ export interface ITreeView extends IDisposable {
634634

635635
canSelectMany: boolean;
636636

637+
canDragAndDrop: boolean;
638+
637639
message?: string;
638640

639641
title: string;
@@ -810,7 +812,7 @@ export interface ITreeViewDataProvider {
810812
readonly isTreeEmpty?: boolean;
811813
onDidChangeEmpty?: Event<void>;
812814
getChildren(element?: ITreeItem): Promise<ITreeItem[]>;
813-
815+
setParent?(elements: ITreeItem[], newParent: ITreeItem): Promise<void>;
814816
}
815817

816818
export interface IEditableData {

src/vs/workbench/test/browser/api/mainThreadTreeViews.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ suite('MainThreadHostTreeView', function () {
7070
}
7171
drain(): any { return null; }
7272
}, new TestViewsService(), new TestNotificationService(), testExtensionService, new NullLogService());
73-
mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false });
73+
mainThreadTreeViews.$registerTreeViewDataProvider(testTreeViewId, { showCollapseAll: false, canSelectMany: false, canDragAndDrop: false });
7474
await testExtensionService.whenInstalledExtensionsRegistered();
7575
});
7676

0 commit comments

Comments
 (0)