extHostTreeViews.ts 7.7 KB
Newer Older
S
Sandeep Somavarapu 已提交
1 2 3 4 5 6 7 8 9
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

import { localize } from 'vs/nls';
import * as vscode from 'vscode';
import URI from 'vs/base/common/uri';
10 11
import { distinct } from 'vs/base/common/arrays';
import { debounceEvent } from 'vs/base/common/event';
S
Sandeep Somavarapu 已提交
12 13
import { TPromise } from 'vs/base/common/winjs.base';
import { Disposable } from 'vs/base/common/lifecycle';
S
Sandeep Somavarapu 已提交
14
import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol';
S
Sandeep Somavarapu 已提交
15
import { ITreeItem, TreeViewItemHandleArg } from 'vs/workbench/parts/views/common/views';
S
Sandeep Somavarapu 已提交
16
import { TreeItemCollapsibleState } from './extHostTypes';
S
Sandeep Somavarapu 已提交
17
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
18
import { asWinJsPromise } from 'vs/base/common/async';
S
Sandeep Somavarapu 已提交
19 20 21

type TreeItemHandle = number;

22
export class ExtHostTreeViews implements ExtHostTreeViewsShape {
S
Sandeep Somavarapu 已提交
23 24 25 26

	private treeViews: Map<string, ExtHostTreeView<any>> = new Map<string, ExtHostTreeView<any>>();

	constructor(
S
Sandeep Somavarapu 已提交
27
		private _proxy: MainThreadTreeViewsShape,
S
Sandeep Somavarapu 已提交
28 29 30 31
		private commands: ExtHostCommands
	) {
		commands.registerArgumentProcessor({
			processArgument: arg => {
S
Sandeep Somavarapu 已提交
32
				if (arg && arg.$treeViewId && arg.$treeItemHandle) {
S
Sandeep Somavarapu 已提交
33 34 35 36 37 38 39
					return this.convertArgument(arg);
				}
				return arg;
			}
		});
	}

S
Sandeep Somavarapu 已提交
40
	registerTreeDataProvider<T>(id: string, treeDataProvider: vscode.TreeDataProvider<T>): vscode.Disposable {
S
Sandeep Somavarapu 已提交
41
		const treeView = new ExtHostTreeView<T>(id, treeDataProvider, this._proxy, this.commands.converter);
S
Sandeep Somavarapu 已提交
42 43 44 45 46 47 48 49 50
		this.treeViews.set(id, treeView);
		return {
			dispose: () => {
				this.treeViews.delete(id);
				treeView.dispose();
			}
		};
	}

S
Sandeep Somavarapu 已提交
51
	$getElements(treeViewId: string): TPromise<ITreeItem[]> {
S
Sandeep Somavarapu 已提交
52 53
		const treeView = this.treeViews.get(treeViewId);
		if (!treeView) {
54
			return TPromise.wrapError<ITreeItem[]>(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)));
S
Sandeep Somavarapu 已提交
55 56 57 58
		}
		return treeView.getTreeItems();
	}

S
Sandeep Somavarapu 已提交
59
	$getChildren(treeViewId: string, treeItemHandle?: number): TPromise<ITreeItem[]> {
S
Sandeep Somavarapu 已提交
60 61
		const treeView = this.treeViews.get(treeViewId);
		if (!treeView) {
62
			return TPromise.wrapError<ITreeItem[]>(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)));
S
Sandeep Somavarapu 已提交
63 64 65 66
		}
		return treeView.getChildren(treeItemHandle);
	}

S
Sandeep Somavarapu 已提交
67 68 69
	private convertArgument(arg: TreeViewItemHandleArg): any {
		const treeView = this.treeViews.get(arg.$treeViewId);
		return treeView ? treeView.getExtensionElement(arg.$treeItemHandle) : null;
S
Sandeep Somavarapu 已提交
70 71 72 73 74 75 76 77 78 79 80
	}
}

class ExtHostTreeView<T> extends Disposable {

	private _itemHandlePool = 0;

	private extElementsMap: Map<TreeItemHandle, T> = new Map<TreeItemHandle, T>();
	private itemHandlesMap: Map<T, TreeItemHandle> = new Map<T, TreeItemHandle>();
	private extChildrenElementsMap: Map<T, T[]> = new Map<T, T[]>();

S
Sandeep Somavarapu 已提交
81
	constructor(private viewId: string, private dataProvider: vscode.TreeDataProvider<T>, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter) {
S
Sandeep Somavarapu 已提交
82 83
		super();
		this.proxy.$registerView(viewId);
S
Sandeep Somavarapu 已提交
84
		if (dataProvider.onDidChangeTreeData) {
85
			this._register(debounceEvent<T, T[]>(dataProvider.onDidChangeTreeData, (last, current) => last ? [...last, current] : [current], 200)(elements => this._refresh(elements)));
S
Sandeep Somavarapu 已提交
86
		}
S
Sandeep Somavarapu 已提交
87 88
	}

S
Sandeep Somavarapu 已提交
89
	getTreeItems(): TPromise<ITreeItem[]> {
S
Sandeep Somavarapu 已提交
90 91 92 93 94 95 96 97
		this.extChildrenElementsMap.clear();
		this.extElementsMap.clear();
		this.itemHandlesMap.clear();

		return asWinJsPromise(() => this.dataProvider.getChildren())
			.then(elements => this.processAndMapElements(elements));
	}

S
Sandeep Somavarapu 已提交
98
	getChildren(treeItemHandle: TreeItemHandle): TPromise<ITreeItem[]> {
S
Sandeep Somavarapu 已提交
99 100 101 102
		let extElement = this.getExtensionElement(treeItemHandle);
		if (extElement) {
			this.clearChildren(extElement);
		} else {
103
			return TPromise.wrapError<ITreeItem[]>(new Error(localize('treeItem.notFound', 'No tree item with id \'{0}\' found.', treeItemHandle)));
S
Sandeep Somavarapu 已提交
104 105 106 107 108 109 110 111 112 113
		}

		return asWinJsPromise(() => this.dataProvider.getChildren(extElement))
			.then(childrenElements => this.processAndMapElements(childrenElements));
	}

	getExtensionElement(treeItemHandle: TreeItemHandle): T {
		return this.extElementsMap.get(treeItemHandle);
	}

114 115 116 117
	private _refresh(elements: T[]): void {
		const hasRoot = elements.some(element => !element);
		if (hasRoot) {
			this.proxy.$refresh(this.viewId, []);
S
Sandeep Somavarapu 已提交
118
		} else {
119 120 121 122 123
			const itemHandles = distinct(elements.map(element => this.itemHandlesMap.get(element))
				.filter(itemHandle => !!itemHandle));
			if (itemHandles.length) {
				this.proxy.$refresh(this.viewId, itemHandles);
			}
S
Sandeep Somavarapu 已提交
124 125 126
		}
	}

S
Sandeep Somavarapu 已提交
127
	private processAndMapElements(elements: T[]): TPromise<ITreeItem[]> {
128 129 130 131 132
		if (elements && elements.length) {
			return TPromise.join(
				elements.filter(element => !!element)
					.map(element => {
						if (this.extChildrenElementsMap.has(element)) {
133
							return TPromise.wrapError<ITreeItem>(new Error(localize('treeView.duplicateElement', 'Element {0} is already registered', element)));
134 135 136 137 138 139
						}
						return this.resolveElement(element);
					}))
				.then(treeItems => treeItems.filter(treeItem => !!treeItem));
		}
		return TPromise.as([]);
S
Sandeep Somavarapu 已提交
140 141 142 143 144 145
	}

	private resolveElement(element: T): TPromise<ITreeItem> {
		return asWinJsPromise(() => this.dataProvider.getTreeItem(element))
			.then(extTreeItem => {
				const treeItem = this.massageTreeItem(extTreeItem);
S
Sandeep Somavarapu 已提交
146 147 148 149
				if (treeItem) {
					this.itemHandlesMap.set(element, treeItem.handle);
					this.extElementsMap.set(treeItem.handle, element);
					if (treeItem.collapsibleState === TreeItemCollapsibleState.Expanded) {
S
Sandeep Somavarapu 已提交
150
						return this.getChildren(treeItem.handle).then(children => {
S
Sandeep Somavarapu 已提交
151 152
							treeItem.children = children;
							return treeItem;
S
Sandeep Somavarapu 已提交
153
						});
S
Sandeep Somavarapu 已提交
154
					} else {
S
Sandeep Somavarapu 已提交
155
						return treeItem;
S
Sandeep Somavarapu 已提交
156 157
					}
				}
S
Sandeep Somavarapu 已提交
158 159
				return null;
			});
S
Sandeep Somavarapu 已提交
160 161
	}

S
Sandeep Somavarapu 已提交
162
	private massageTreeItem(extensionTreeItem: vscode.TreeItem): ITreeItem {
S
Sandeep Somavarapu 已提交
163 164 165
		if (!extensionTreeItem) {
			return null;
		}
166
		const icon = this.getLightIconPath(extensionTreeItem);
S
Sandeep Somavarapu 已提交
167 168 169
		return {
			handle: ++this._itemHandlePool,
			label: extensionTreeItem.label,
S
Sandeep Somavarapu 已提交
170
			command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command) : void 0,
S
Sandeep Somavarapu 已提交
171
			contextValue: extensionTreeItem.contextValue,
172 173
			icon,
			iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
S
Sandeep Somavarapu 已提交
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
			collapsibleState: extensionTreeItem.collapsibleState,
		};
	}

	private getLightIconPath(extensionTreeItem: vscode.TreeItem) {
		if (extensionTreeItem.iconPath) {
			if (typeof extensionTreeItem.iconPath === 'string' || extensionTreeItem.iconPath instanceof URI) {
				return this.getIconPath(extensionTreeItem.iconPath);
			}
			return this.getIconPath(extensionTreeItem.iconPath['light']);
		}
		return void 0;
	}

	private getDarkIconPath(extensionTreeItem: vscode.TreeItem) {
		if (extensionTreeItem.iconPath && extensionTreeItem.iconPath['dark']) {
			return this.getIconPath(extensionTreeItem.iconPath['dark']);
		}
		return void 0;
	}

	private getIconPath(iconPath: string | URI): string {
		if (iconPath instanceof URI) {
			return iconPath.toString();
		}
		return URI.file(iconPath).toString();
	}

	private clearChildren(extElement: T): void {
		const children = this.extChildrenElementsMap.get(extElement);
		if (children) {
			for (const child of children) {
				this.clearElement(child);
			}
			this.extChildrenElementsMap.delete(extElement);
		}
	}

	private clearElement(extElement: T): void {
		this.clearChildren(extElement);

		const treeItemhandle = this.itemHandlesMap.get(extElement);
		this.itemHandlesMap.delete(extElement);
		if (treeItemhandle) {
			this.extElementsMap.delete(treeItemhandle);
		}
	}

	dispose() {
		this.extElementsMap.clear();
		this.itemHandlesMap.clear();
		this.extChildrenElementsMap.clear();
	}
}