Skip to content

Commit

Permalink
[plugin] implement selection and visible tree view APIs
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Kosyakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Aug 26, 2019
1 parent 1f7501e commit 5c82b09
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 100 deletions.
2 changes: 2 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ export interface TreeViewsMain {
export interface TreeViewsExt {
$getChildren(treeViewId: string, treeItemId: string | undefined): Promise<TreeViewItem[] | undefined>;
$setExpanded(treeViewId: string, treeItemId: string, expanded: boolean): Promise<any>;
$setSelection(treeViewId: string, treeItemIds: string[]): Promise<void>;
$setVisible(treeViewId: string, visible: boolean): Promise<void>;
}

export class TreeViewItem {
Expand Down
20 changes: 20 additions & 0 deletions packages/plugin-ext/src/main/browser/view/tree-view-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ import { MenuPath, MenuModelRegistry, ActionMenuNode } from '@theia/core/lib/com
import * as React from 'react';
import { PluginSharedStyle } from '../plugin-shared-style';
import { ViewContextKeyService } from './view-context-key-service';
import { Widget } from '@theia/core/lib/browser/widgets/widget';
import { CommandRegistry } from '@theia/core/lib/common/command';
import { Emitter } from '@theia/core/lib/common/event';
import { MessageService } from '@theia/core/lib/common/message-service';
import { View } from '../../../common/plugin-protocol';

Expand Down Expand Up @@ -205,12 +207,16 @@ export class TreeViewWidget extends TreeWidget {
@inject(PluginTreeModel)
readonly model: PluginTreeModel;

protected readonly onDidChangeVisibilityEmitter = new Emitter<boolean>();
readonly onDidChangeVisibility = this.onDidChangeVisibilityEmitter.event;

@postConstruct()
protected init(): void {
super.init();
this.id = this.identifier.id;
this.addClass('theia-tree-view');
this.node.style.height = '100%';
this.toDispose.push(this.onDidChangeVisibilityEmitter);
}

protected renderIcon(node: TreeNode, props: NodeProps): React.ReactNode {
Expand Down Expand Up @@ -317,4 +323,18 @@ export class TreeViewWidget extends TreeWidget {
return [this.toTreeViewSelection(node)];
}

setFlag(flag: Widget.Flag): void {
super.setFlag(flag);
if (flag === Widget.Flag.IsVisible) {
this.onDidChangeVisibilityEmitter.fire(this.isVisible);
}
}

clearFlag(flag: Widget.Flag): void {
super.clearFlag(flag);
if (flag === Widget.Flag.IsVisible) {
this.onDidChangeVisibilityEmitter.fire(this.isVisible);
}
}

}
6 changes: 6 additions & 0 deletions packages/plugin-ext/src/main/browser/view/tree-views-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,18 @@ export class TreeViewsMainImpl implements TreeViewsMain {
}
this.contextKeys.view.set(treeViewId);

this.proxy.$setSelection(treeViewId, event.map((node: TreeViewNode) => node.id));

// execute TreeItem.command if present
const treeNode = event[0] as TreeViewNode;
if (treeNode && treeNode.command) {
this.commands.executeCommand(treeNode.command.id, ...(treeNode.command.arguments || []));
}
});

const updateVisible = () => this.proxy.$setVisible(treeViewId, treeViewWidget.isVisible);
updateVisible();
treeViewWidget.onDidChangeVisibility(() => updateVisible());
}

}
118 changes: 102 additions & 16 deletions packages/plugin-ext/src/plugin/tree/tree-views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@

import * as path from 'path';
import URI from 'vscode-uri';
import { TreeDataProvider, TreeView, TreeViewExpansionEvent, TreeItem2, TreeItemLabel } from '@theia/plugin';
import {
TreeDataProvider, TreeView, TreeViewExpansionEvent, TreeItem2, TreeItemLabel,
TreeViewSelectionChangeEvent, TreeViewVisibilityChangeEvent
} from '@theia/plugin';
// TODO: extract `@theia/util` for event, disposable, cancellation and common types
// don't use @theia/core directly from plugin host
import { Emitter } from '@theia/core/lib/common/event';
import { Disposable, ThemeIcon } from '../types-impl';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
import { Disposable as PluginDisposable, ThemeIcon } from '../types-impl';
import { Plugin, PLUGIN_RPC_CONTEXT, TreeViewsExt, TreeViewsMain, TreeViewItem } from '../../common/plugin-api-rpc';
import { RPCProtocol } from '../../common/rpc-protocol';
import { CommandRegistryImpl } from '../command-registry';
Expand All @@ -47,10 +53,10 @@ export class TreeViewsExtImpl implements TreeViewsExt {
});
}

registerTreeDataProvider<T>(plugin: Plugin, treeViewId: string, treeDataProvider: TreeDataProvider<T>): Disposable {
registerTreeDataProvider<T>(plugin: Plugin, treeViewId: string, treeDataProvider: TreeDataProvider<T>): PluginDisposable {
const treeView = this.createTreeView(plugin, treeViewId, { treeDataProvider });

return Disposable.create(() => {
return PluginDisposable.create(() => {
this.treeViews.delete(treeViewId);
treeView.dispose();
});
Expand All @@ -77,7 +83,18 @@ export class TreeViewsExtImpl implements TreeViewsExt {
get selection() {
return treeView.selectedElements;
},

// tslint:disable-next-line:typedef
get onDidChangeSelection() {
return treeView.onDidChangeSelection;
},
// tslint:disable-next-line:typedef
get visible() {
return treeView.visible;
},
// tslint:disable-next-line:typedef
get onDidChangeVisibility() {
return treeView.onDidChangeVisibility;
},
reveal: (element: T, selectionOptions: { select?: boolean }): Thenable<void> =>
treeView.reveal(element, selectionOptions),

Expand Down Expand Up @@ -110,32 +127,55 @@ export class TreeViewsExtImpl implements TreeViewsExt {
}
}

async $setSelection(treeViewId: string, treeItemIds: string[]): Promise<void> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
throw new Error('No tree view with id' + treeViewId);
}
treeView.setSelection(treeItemIds);
}

async $setVisible(treeViewId: string, isVisible: boolean): Promise<void> {
const treeView = this.treeViews.get(treeViewId);
if (!treeView) {
throw new Error('No tree view with id' + treeViewId);
}
treeView.setVisible(isVisible);
}

}

class TreeViewExtImpl<T> extends Disposable {
class TreeViewExtImpl<T> implements Disposable {

private readonly onDidExpandElementEmitter = new Emitter<TreeViewExpansionEvent<T>>();
readonly onDidExpandElement = this.onDidExpandElementEmitter.event;

private onDidExpandElementEmitter: Emitter<TreeViewExpansionEvent<T>> = new Emitter<TreeViewExpansionEvent<T>>();
public readonly onDidExpandElement = this.onDidExpandElementEmitter.event;
private readonly onDidCollapseElementEmitter = new Emitter<TreeViewExpansionEvent<T>>();
readonly onDidCollapseElement = this.onDidCollapseElementEmitter.event;

private onDidCollapseElementEmitter: Emitter<TreeViewExpansionEvent<T>> = new Emitter<TreeViewExpansionEvent<T>>();
public readonly onDidCollapseElement = this.onDidCollapseElementEmitter.event;
private readonly onDidChangeSelectionEmitter = new Emitter<TreeViewSelectionChangeEvent<T>>();
readonly onDidChangeSelection = this.onDidChangeSelectionEmitter.event;

private selection: T[] = [];
get selectedElements(): T[] { return this.selection; }
private readonly onDidChangeVisibilityEmitter = new Emitter<TreeViewVisibilityChangeEvent>();
readonly onDidChangeVisibility = this.onDidChangeVisibilityEmitter.event;

private cache: Map<string, T> = new Map<string, T>();

private readonly toDispose = new DisposableCollection(
this.onDidExpandElementEmitter,
this.onDidCollapseElementEmitter,
this.onDidChangeSelectionEmitter,
this.onDidChangeVisibilityEmitter
);

constructor(
private plugin: Plugin,
private treeViewId: string,
private treeDataProvider: TreeDataProvider<T>,
private proxy: TreeViewsMain) {

super(() => {
proxy.$unregisterTreeDataProvider(treeViewId);
});

proxy.$registerTreeDataProvider(treeViewId);
this.toDispose.push(Disposable.create(() => this.proxy.$unregisterTreeDataProvider(treeViewId)));

if (treeDataProvider.onDidChangeTreeData) {
treeDataProvider.onDidChangeTreeData((e: T) => {
Expand All @@ -144,6 +184,10 @@ class TreeViewExtImpl<T> extends Disposable {
}
}

dispose(): void {
this.toDispose.dispose();
}

async reveal(element: T, selectionOptions?: { select?: boolean }): Promise<void> {
// find element id in a cache
let elementId;
Expand Down Expand Up @@ -294,4 +338,46 @@ class TreeViewExtImpl<T> extends Disposable {
}
}

private selectedItemIds = new Set<string>();
get selectedElements(): T[] {
const items: T[] = [];
for (const id of this.selectedItemIds) {
const item = this.getTreeItem(id);
if (item) {
items.push(item);
}
}
return items;
}

setSelection(selectedItemIds: string[]): void {
const toDelete = new Set<string>(this.selectedItemIds);
for (const id of this.selectedItemIds) {
toDelete.delete(id);
if (!this.selectedItemIds.has(id)) {
this.doSetSelection(selectedItemIds);
return;
}
}
if (toDelete.size) {
this.doSetSelection(selectedItemIds);
}
}
protected doSetSelection(selectedItemIts: string[]): void {
this.selectedItemIds = new Set(selectedItemIts);
this.onDidChangeSelectionEmitter.fire(Object.freeze({ selection: this.selectedElements }));
}

private _visible = false;
get visible(): boolean {
return this._visible;
}

setVisible(visible: boolean): void {
if (visible !== this._visible) {
this._visible = visible;
this.onDidChangeVisibilityEmitter.fire(Object.freeze({ visible: this._visible }));
}
}

}
Loading

0 comments on commit 5c82b09

Please sign in to comment.