Skip to content

Commit

Permalink
Multi-select in custom tree view (#78625)
Browse files Browse the repository at this point in the history
Part of #76941

The first argument is now the element that the command is executed on. The second argurment is an array of the other selected items
  • Loading branch information
alexr00 authored Aug 15, 2019
1 parent 7c53175 commit cdea3dc
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 23 deletions.
9 changes: 9 additions & 0 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,15 @@ declare module 'vscode' {
*/
constructor(label: TreeItemLabel, collapsibleState?: TreeItemCollapsibleState);
}

export interface TreeViewOptions2<T> extends TreeViewOptions<T> {
/**
* Whether the tree supports multi-select. When the tree supports multi-select and a command is executed from the tree,
* the first argument to the command is the tree item that the command was executed on and the second argument is an
* array containing the other selected tree items.
*/
canSelectMany?: boolean;
}
//#endregion

//#region CustomExecution
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/api/browser/mainThreadTreeViews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews);
}

$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean }): void {
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): void {
const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService);
this._dataProviders.set(treeViewId, dataProvider);
const viewer = this.getTreeView(treeViewId);
if (viewer) {
viewer.dataProvider = dataProvider;
viewer.showCollapseAllAction = !!options.showCollapseAll;
viewer.canSelectMany = !!options.canSelectMany;
this.registerListeners(treeViewId, viewer);
this._proxy.$setVisible(treeViewId, viewer.visible);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ export interface MainThreadTextEditorsShape extends IDisposable {
}

export interface MainThreadTreeViewsShape extends IDisposable {
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean }): void;
$registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): void;
$refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise<void>;
$reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise<void>;
$setMessage(treeViewId: string, message: string): void;
Expand Down
17 changes: 14 additions & 3 deletions src/vs/workbench/api/common/extHostTreeViews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,21 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
private commands: ExtHostCommands,
private logService: ILogService
) {

function isTreeViewItemHandleArg(arg: any): boolean {
return arg && arg.$treeViewId && arg.$treeItemHandle;
}
commands.registerArgumentProcessor({
processArgument: arg => {
if (arg && arg.$treeViewId && arg.$treeItemHandle) {
if (isTreeViewItemHandleArg(arg)) {
return this.convertArgument(arg);
} else if (Array.isArray(arg) && (arg.length > 0)) {
return arg.map(item => {
if (isTreeViewItemHandleArg(item)) {
return this.convertArgument(item);
}
return item;
});
}
return arg;
}
Expand Down Expand Up @@ -182,10 +193,10 @@ class ExtHostTreeView<T> extends Disposable {
private refreshPromise: Promise<void> = Promise.resolve();
private refreshQueue: Promise<void> = Promise.resolve();

constructor(private viewId: string, options: vscode.TreeViewOptions<T>, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) {
constructor(private viewId: string, options: vscode.TreeViewOptions2<T>, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) {
super();
this.dataProvider = options.treeDataProvider;
this.proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll });
this.proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany });
if (this.dataProvider.onDidChangeTreeData) {
this._register(this.dataProvider.onDidChangeTreeData(element => this._onDidChangeData.fire({ message: false, element })));
}
Expand Down
57 changes: 39 additions & 18 deletions src/vs/workbench/browser/parts/views/customView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,11 @@ export class CustomTreeView extends Disposable implements ITreeView {
private domNode!: HTMLElement;
private treeContainer!: HTMLElement;
private _messageValue: string | undefined;
private _canSelectMany: boolean = false;
private messageElement!: HTMLDivElement;
private tree: WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore> | undefined;
private treeLabels: ResourceLabels | undefined;

private root: ITreeItem;
private elementsToRefresh: ITreeItem[] = [];
private menus: TitleMenus;
Expand Down Expand Up @@ -253,6 +255,14 @@ export class CustomTreeView extends Disposable implements ITreeView {
this.updateMessage();
}

get canSelectMany(): boolean {
return this._canSelectMany;
}

set canSelectMany(canSelectMany: boolean) {
this._canSelectMany = canSelectMany;
}

get hasIconForParentNode(): boolean {
return this._hasIconForParentNode;
}
Expand Down Expand Up @@ -372,12 +382,14 @@ export class CustomTreeView extends Disposable implements ITreeView {
collapseByDefault: (e: ITreeItem): boolean => {
return e.collapsibleState !== TreeItemCollapsibleState.Expanded;
},
multipleSelectionSupport: false
}));
multipleSelectionSupport: this.canSelectMany,
}) as WorkbenchAsyncDataTree<ITreeItem, ITreeItem, FuzzyScore>);
aligner.tree = this.tree;
const actionRunner = new MultipleSelectionActionRunner(() => this.tree!.getSelection());
renderer.actionRunner = actionRunner;

this.tree.contextKeyService.createKey<boolean>(this.id, true);
this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e)));
this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner)));
this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements)));
this._register(this.tree.onDidChangeCollapseState(e => {
if (!e.node.element) {
Expand Down Expand Up @@ -406,7 +418,7 @@ export class CustomTreeView extends Disposable implements ITreeView {
}));
}

private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent<ITreeItem>): void {
private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent<ITreeItem>, actionRunner: MultipleSelectionActionRunner): void {
const node: ITreeItem | null = treeEvent.element;
if (node === null) {
return;
Expand Down Expand Up @@ -442,7 +454,7 @@ export class CustomTreeView extends Disposable implements ITreeView {

getActionsContext: () => (<TreeViewItemHandleArg>{ $treeViewId: this.id, $treeItemHandle: node.handle }),

actionRunner: new MultipleSelectionActionRunner(() => this.tree!.getSelection())
actionRunner
});
}

Expand Down Expand Up @@ -686,6 +698,8 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
static readonly ITEM_HEIGHT = 22;
static readonly TREE_TEMPLATE_ID = 'treeExplorer';

private _actionRunner: MultipleSelectionActionRunner | undefined;

constructor(
private treeViewId: string,
private menus: TreeMenus,
Expand All @@ -703,6 +717,10 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
return TreeRenderer.TREE_TEMPLATE_ID;
}

set actionRunner(actionRunner: MultipleSelectionActionRunner) {
this._actionRunner = actionRunner;
}

renderTemplate(container: HTMLElement): ITreeExplorerTemplateData {
DOM.addClass(container, 'custom-view-tree-node-item');

Expand Down Expand Up @@ -740,8 +758,11 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS

templateData.icon.style.backgroundImage = iconUrl ? `url('${DOM.asDomUri(iconUrl).toString(true)}')` : '';
DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!iconUrl);
templateData.actionBar.context = (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle });
templateData.actionBar.context = <TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle };
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
if (this._actionRunner) {
templateData.actionBar.actionRunner = this._actionRunner;
}
this.setAlignment(templateData.container, node);
templateData.elementDisposable = (this.themeService.onDidFileIconThemeChange(() => this.setAlignment(templateData.container, node)));
}
Expand Down Expand Up @@ -822,23 +843,23 @@ class Aligner extends Disposable {

class MultipleSelectionActionRunner extends ActionRunner {

constructor(private getSelectedResources: (() => any[])) {
constructor(private getSelectedResources: (() => ITreeItem[])) {
super();
}

runAction(action: IAction, context: any): Promise<any> {
if (action instanceof MenuItemAction) {
const selection = this.getSelectedResources();
const filteredSelection = selection.filter(s => s !== context);

if (selection.length === filteredSelection.length || selection.length === 1) {
return action.run(context);
}

return action.run(context, ...filteredSelection);
runAction(action: IAction, context: TreeViewItemHandleArg): Promise<any> {
const selection = this.getSelectedResources();
let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined;
if (selection.length > 1) {
selectionHandleArgs = [];
selection.forEach(selected => {
if (selected.handle !== context.$treeItemHandle) {
selectionHandleArgs!.push({ $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle });
}
});
}

return super.runAction(action, context);
return action.run(...[context, selectionHandleArgs]);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/common/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ export interface ITreeView extends IDisposable {

showCollapseAllAction: boolean;

canSelectMany: boolean;

message?: string;

readonly visible: boolean;
Expand Down

0 comments on commit cdea3dc

Please sign in to comment.