extHostTreeViews.ts 10.5 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
import { debounceEvent } from 'vs/base/common/event';
S
Sandeep Somavarapu 已提交
11 12
import { TPromise } from 'vs/base/common/winjs.base';
import { Disposable } from 'vs/base/common/lifecycle';
S
Sandeep Somavarapu 已提交
13
import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol';
14
import { ITreeItem, TreeViewItemHandleArg } from 'vs/workbench/common/views';
S
Sandeep Somavarapu 已提交
15
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
16
import { asWinJsPromise } from 'vs/base/common/async';
S
Sandeep Somavarapu 已提交
17

S
Sandeep Somavarapu 已提交
18
type TreeItemHandle = string;
S
Sandeep Somavarapu 已提交
19

20
export class ExtHostTreeViews implements ExtHostTreeViewsShape {
S
Sandeep Somavarapu 已提交
21 22 23 24

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

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

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

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

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

S
Sandeep Somavarapu 已提交
65 66 67
	private convertArgument(arg: TreeViewItemHandleArg): any {
		const treeView = this.treeViews.get(arg.$treeViewId);
		return treeView ? treeView.getExtensionElement(arg.$treeItemHandle) : null;
S
Sandeep Somavarapu 已提交
68 69 70
	}
}

71
interface TreeNode {
S
Sandeep Somavarapu 已提交
72
	index: number;
73 74 75 76 77
	handle: TreeItemHandle;
	parent: TreeItemHandle;
	children: TreeItemHandle[];
}

S
Sandeep Somavarapu 已提交
78 79
class ExtHostTreeView<T> extends Disposable {

S
Sandeep Somavarapu 已提交
80 81
	private static ROOT_HANDLE = '0';
	private elements: Map<TreeItemHandle, T> = new Map<TreeItemHandle, T>();
82
	private nodes: Map<T, TreeNode> = new Map<T, TreeNode>();
S
Sandeep Somavarapu 已提交
83

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

S
Sandeep Somavarapu 已提交
92
	getTreeItems(): TPromise<ITreeItem[]> {
93
		this.clearAll();
S
Sandeep Somavarapu 已提交
94
		return asWinJsPromise(() => this.dataProvider.getChildren())
S
Sandeep Somavarapu 已提交
95
			.then(elements => this.resolveElements(elements));
S
Sandeep Somavarapu 已提交
96 97
	}

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
		}

		return asWinJsPromise(() => this.dataProvider.getChildren(extElement))
107 108 109 110 111
			.then(childrenElements => this.resolveElements(childrenElements, treeItemHandle))
			.then(childrenItems => {
				this.nodes.get(extElement).children = childrenItems.map(c => c.handle);
				return childrenItems;
			});
S
Sandeep Somavarapu 已提交
112 113 114
	}

	getExtensionElement(treeItemHandle: TreeItemHandle): T {
S
Sandeep Somavarapu 已提交
115
		return this.elements.get(treeItemHandle);
S
Sandeep Somavarapu 已提交
116 117
	}

118 119 120
	private _refresh(elements: T[]): void {
		const hasRoot = elements.some(element => !element);
		if (hasRoot) {
S
Sandeep Somavarapu 已提交
121
			this.proxy.$refresh(this.viewId);
S
Sandeep Somavarapu 已提交
122
		} else {
S
Sandeep Somavarapu 已提交
123
			const handlesToUpdate = this.getHandlesToUpdate(elements);
124
			if (handlesToUpdate.length) {
S
Sandeep Somavarapu 已提交
125
				this._refreshHandles(handlesToUpdate);
126
			}
S
Sandeep Somavarapu 已提交
127 128 129
		}
	}

S
Sandeep Somavarapu 已提交
130
	private resolveElements(elements: T[], parentHandle?: TreeItemHandle): TPromise<ITreeItem[]> {
131 132 133
		if (elements && elements.length) {
			return TPromise.join(
				elements.filter(element => !!element)
S
Sandeep Somavarapu 已提交
134
					.map((element, index) => {
135 136 137 138
						return this.resolveElement(element, index, parentHandle)
							.then(treeItem => {
								if (treeItem) {
									this.nodes.set(element, {
S
Sandeep Somavarapu 已提交
139
										index,
140 141 142 143
										handle: treeItem.handle,
										parent: parentHandle,
										children: void 0
									});
144 145 146
									if (this.elements.has(treeItem.handle)) {
										return TPromise.wrapError<ITreeItem>(new Error(localize('treeView.duplicateElement', 'Element {0} is already registered', element)));
									}
S
Sandeep Somavarapu 已提交
147
									this.elements.set(treeItem.handle, element);
148 149 150
								}
								return treeItem;
							});
151 152 153 154
					}))
				.then(treeItems => treeItems.filter(treeItem => !!treeItem));
		}
		return TPromise.as([]);
S
Sandeep Somavarapu 已提交
155 156
	}

S
Sandeep Somavarapu 已提交
157
	private resolveElement(element: T, index: number, parentHandle?: TreeItemHandle): TPromise<ITreeItem> {
S
Sandeep Somavarapu 已提交
158
		return asWinJsPromise(() => this.dataProvider.getTreeItem(element))
159
			.then(extTreeItem => this.massageTreeItem(element, extTreeItem, index, parentHandle));
S
Sandeep Somavarapu 已提交
160 161
	}

162
	private massageTreeItem(element: T, extensionTreeItem: vscode.TreeItem, index: number, parentHandle: TreeItemHandle): ITreeItem {
S
Sandeep Somavarapu 已提交
163 164 165
		if (!extensionTreeItem) {
			return null;
		}
166
		const icon = this.getLightIconPath(extensionTreeItem);
167
		const label = extensionTreeItem.label;
168
		const handle = typeof element === 'string' ? element : this.generateHandle(label, index, parentHandle);
S
Sandeep Somavarapu 已提交
169
		return {
170
			handle,
S
Sandeep Somavarapu 已提交
171
			parentHandle,
172
			label,
S
Sandeep Somavarapu 已提交
173
			command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command) : void 0,
S
Sandeep Somavarapu 已提交
174
			contextValue: extensionTreeItem.contextValue,
175 176
			icon,
			iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
177
			collapsibleState: extensionTreeItem.collapsibleState
S
Sandeep Somavarapu 已提交
178 179 180
		};
	}

181 182 183 184 185 186
	private generateHandle(label: string, index: number, parentHandle: TreeItemHandle): TreeItemHandle {
		parentHandle = parentHandle ? parentHandle : ExtHostTreeView.ROOT_HANDLE;
		label = label.indexOf('/') !== -1 ? label.replace('/', '//') : label;
		return `${parentHandle}/${index}:${label}`;
	}

187
	private getLightIconPath(extensionTreeItem: vscode.TreeItem): string {
S
Sandeep Somavarapu 已提交
188 189 190 191 192 193 194 195 196
		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;
	}

197
	private getDarkIconPath(extensionTreeItem: vscode.TreeItem): string {
S
Sandeep Somavarapu 已提交
198 199 200 201 202 203 204 205 206 207 208 209 210
		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();
	}

S
Sandeep Somavarapu 已提交
211
	private getHandlesToUpdate(elements: T[]): TreeItemHandle[] {
212 213 214 215 216 217 218
		const elementsToUpdate = new Set<TreeItemHandle>();
		for (const element of elements) {
			let elementNode = this.nodes.get(element);
			if (elementNode && !elementsToUpdate.has(elementNode.handle)) {
				// check if an ancestor of extElement is already in the elements to update list
				let currentNode = elementNode;
				while (currentNode && currentNode.parent && !elementsToUpdate.has(currentNode.parent)) {
S
Sandeep Somavarapu 已提交
219
					const parentElement = this.elements.get(currentNode.parent);
220 221
					currentNode = this.nodes.get(parentElement);
				}
S
Sandeep Somavarapu 已提交
222
				if (!currentNode.parent) {
223 224
					elementsToUpdate.add(elementNode.handle);
				}
S
Sandeep Somavarapu 已提交
225 226
			}
		}
227 228 229 230

		const handlesToUpdate: TreeItemHandle[] = [];
		// Take only top level elements
		elementsToUpdate.forEach((handle) => {
S
Sandeep Somavarapu 已提交
231
			const element = this.elements.get(handle);
232 233 234 235 236 237 238
			let node = this.nodes.get(element);
			if (node && !elementsToUpdate.has(node.parent)) {
				handlesToUpdate.push(handle);
			}
		});

		return handlesToUpdate;
S
Sandeep Somavarapu 已提交
239 240
	}

S
Sandeep Somavarapu 已提交
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
	private _refreshHandles(itemHandles: TreeItemHandle[]): TPromise<void> {
		const itemsToRefresh: { [handle: string]: ITreeItem } = {};
		const promises: TPromise<void>[] = [];
		itemHandles.forEach(treeItemHandle => {
			const extElement = this.getExtensionElement(treeItemHandle);
			const node = this.nodes.get(extElement);
			const promise = this.resolveElement(extElement, node.index, node.parent)
				.then(treeItem => {
					if (treeItemHandle !== treeItem.handle) {
						// Update caches if handle changes
						this.updateCaches(node, treeItem, extElement);
					}
					itemsToRefresh[treeItemHandle] = treeItem;
				});
			promises.push(promise);
		});
		return TPromise.join(promises)
			.then(treeItems => {
				this.proxy.$refresh(this.viewId, itemsToRefresh);
			});
	}

	private updateCaches(node: TreeNode, treeItem: ITreeItem, element: T): void {
		if (node.parent) {
			// Update parent's children handles
			const parentElement = this.getExtensionElement(node.parent);
			const parentNode = this.nodes.get(parentElement);
			parentNode.children[node.index] = treeItem.handle;
		}

		// Update elements map
		this.elements.delete(node.handle);
		this.elements.set(treeItem.handle, element);

		// Update node
		node.handle = treeItem.handle;
	}

279 280 281 282
	private clearChildren(element: T): void {
		let node = this.nodes.get(element);
		if (node.children) {
			for (const childHandle of node.children) {
S
Sandeep Somavarapu 已提交
283
				const childEleement = this.elements.get(childHandle);
284 285 286 287 288 289 290
				if (childEleement) {
					this.clear(childEleement);
				}
			}
		}
		node.children = void 0;
	}
S
Sandeep Somavarapu 已提交
291

292 293 294 295
	private clear(element: T): void {
		let node = this.nodes.get(element);
		if (node.children) {
			for (const childHandle of node.children) {
S
Sandeep Somavarapu 已提交
296
				const childEleement = this.elements.get(childHandle);
297 298 299 300
				if (childEleement) {
					this.clear(childEleement);
				}
			}
S
Sandeep Somavarapu 已提交
301
		}
302
		this.nodes.delete(element);
S
Sandeep Somavarapu 已提交
303
		this.elements.delete(node.handle);
S
Sandeep Somavarapu 已提交
304 305
	}

306
	private clearAll(): void {
S
Sandeep Somavarapu 已提交
307
		this.elements.clear();
308 309 310 311 312
		this.nodes.clear();
	}

	dispose() {
		this.clearAll();
S
Sandeep Somavarapu 已提交
313 314
	}
}