extHostTreeViews.ts 10.1 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
import { coalesce } from 'vs/base/common/arrays';
S
Sandeep Somavarapu 已提交
18

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

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

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

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

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

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

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

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

72 73
interface TreeNode {
	handle: TreeItemHandle;
S
Sandeep Somavarapu 已提交
74 75
	parentHandle: TreeItemHandle;
	childrenHandles: TreeItemHandle[];
76 77
}

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

80 81 82
	private static LABEL_HANDLE_PREFIX = '0';

	private rootHandles: TreeItemHandle[] = [];
S
Sandeep Somavarapu 已提交
83
	private elements: Map<TreeItemHandle, T> = new Map<TreeItemHandle, T>();
84
	private nodes: Map<T, TreeNode> = new Map<T, TreeNode>();
S
Sandeep Somavarapu 已提交
85

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

S
Sandeep Somavarapu 已提交
94
	getChildren(parentHandle?: TreeItemHandle): TPromise<ITreeItem[]> {
95 96 97 98
		const parentElement = parentHandle ? this.getExtensionElement(parentHandle) : void 0;
		if (parentHandle && !parentElement) {
			console.error(`No tree item with id \'${parentHandle}\' found.`);
			return TPromise.as([]);
S
Sandeep Somavarapu 已提交
99 100
		}

101
		this.clearChildren(parentElement);
S
Sandeep Somavarapu 已提交
102
		return asWinJsPromise(() => this.dataProvider.getChildren(parentElement))
103 104 105 106 107 108
			.then(elements => TPromise.join(
				coalesce(elements || []).map(element =>
					asWinJsPromise(() => this.dataProvider.getTreeItem(element))
						.then(extTreeItem => {
							if (extTreeItem) {
								return { element, extTreeItem };
S
Sandeep Somavarapu 已提交
109
							}
110 111 112
							return null;
						})
				))).then(extTreeItems => extTreeItems.map((({ element, extTreeItem }) => this.createTreeItem(element, extTreeItem, parentHandle))));
S
Sandeep Somavarapu 已提交
113 114 115
	}

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

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

131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
	private getHandlesToRefresh(elements: T[]): TreeItemHandle[] {
		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.parentHandle && !elementsToUpdate.has(currentNode.parentHandle)) {
					const parentElement = this.elements.get(currentNode.parentHandle);
					currentNode = this.nodes.get(parentElement);
				}
				if (!currentNode.parentHandle) {
					elementsToUpdate.add(elementNode.handle);
				}
			}
		}

		const handlesToUpdate: TreeItemHandle[] = [];
		// Take only top level elements
		elementsToUpdate.forEach((handle) => {
			const element = this.elements.get(handle);
			let node = this.nodes.get(element);
			if (node && !elementsToUpdate.has(node.parentHandle)) {
				handlesToUpdate.push(handle);
			}
		});

		return handlesToUpdate;
	}

	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);
			promises.push(asWinJsPromise(() => this.dataProvider.getTreeItem(extElement))
				.then(extTreeItem => {
					if (extTreeItem) {
						itemsToRefresh[treeItemHandle] = this.createTreeItem(extElement, extTreeItem, node.parentHandle);
					}
				}));
		});
		return TPromise.join(promises)
			.then(treeItems => this.proxy.$refresh(this.viewId, itemsToRefresh));
S
Sandeep Somavarapu 已提交
176 177
	}

178
	private createTreeItem(element: T, extensionTreeItem: vscode.TreeItem, parentHandle: TreeItemHandle): ITreeItem {
S
Sandeep Somavarapu 已提交
179

180
		const handle = this.createHandle(element, extensionTreeItem, parentHandle);
181
		const icon = this.getLightIconPath(extensionTreeItem);
182
		this.update(element, handle, parentHandle);
S
Sandeep Somavarapu 已提交
183

S
Sandeep Somavarapu 已提交
184
		return {
185
			handle,
S
Sandeep Somavarapu 已提交
186
			parentHandle,
187
			label: extensionTreeItem.label,
S
Sandeep Somavarapu 已提交
188
			command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command) : void 0,
S
Sandeep Somavarapu 已提交
189
			contextValue: extensionTreeItem.contextValue,
190 191
			icon,
			iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
192
			collapsibleState: extensionTreeItem.collapsibleState
S
Sandeep Somavarapu 已提交
193 194 195
		};
	}

196 197
	private createHandle(element: T, { label }: vscode.TreeItem, parentHandle?: TreeItemHandle): TreeItemHandle {
		const prefix = parentHandle ? parentHandle : ExtHostTreeView.LABEL_HANDLE_PREFIX;
S
Sandeep Somavarapu 已提交
198
		label = label.indexOf('/') !== -1 ? label.replace('/', '//') : label;
199 200 201 202 203 204 205 206 207 208
		const existingHandle = this.nodes.has(element) ? this.nodes.get(element).handle : void 0;

		for (let labelCount = 0; labelCount <= this.getChildrenHandles(parentHandle).length; labelCount++) {
			const handle = `${prefix}/${labelCount}:${label}`;
			if (!this.elements.has(handle) || existingHandle === handle) {
				return handle;
			}
		}

		throw new Error('This should not be reached');
S
Sandeep Somavarapu 已提交
209 210
	}

211
	private getLightIconPath(extensionTreeItem: vscode.TreeItem): string {
S
Sandeep Somavarapu 已提交
212 213 214 215 216 217 218 219 220
		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;
	}

221
	private getDarkIconPath(extensionTreeItem: vscode.TreeItem): string {
S
Sandeep Somavarapu 已提交
222 223 224 225 226 227 228 229 230 231 232 233 234
		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();
	}

235 236 237
	private getChildrenHandles(parentHandle?: TreeItemHandle): TreeItemHandle[] {
		return parentHandle ? this.nodes.get(this.getExtensionElement(parentHandle)).childrenHandles : this.rootHandles;
	}
238

239 240 241
	private update(element: T, handle: TreeItemHandle, parentHandle: TreeItemHandle): void {
		const node = this.nodes.get(element);
		const childrenHandles = this.getChildrenHandles(parentHandle);
242

243 244 245 246 247 248 249 250 251
		// Update parent node
		if (node) {
			if (node.handle !== handle) {
				childrenHandles[childrenHandles.indexOf(node.handle)] = handle;
				this.clearChildren(element);
			}
		} else {
			childrenHandles.push(handle);
		}
S
Sandeep Somavarapu 已提交
252

253 254 255 256 257 258
		// Update element maps
		this.elements.set(handle, element);
		this.nodes.set(element, {
			handle,
			parentHandle,
			childrenHandles: node ? node.childrenHandles : []
S
Sandeep Somavarapu 已提交
259 260 261
		});
	}

262
	private clearChildren(parentElement?: T): void {
S
Sandeep Somavarapu 已提交
263
		if (parentElement) {
264 265 266 267 268 269 270
			let node = this.nodes.get(parentElement);
			if (node.childrenHandles) {
				for (const childHandle of node.childrenHandles) {
					const childEleement = this.elements.get(childHandle);
					if (childEleement) {
						this.clear(childEleement);
					}
S
Sandeep Somavarapu 已提交
271
				}
272
			}
273 274 275
			node.childrenHandles = [];
		} else {
			this.clearAll();
276 277
		}
	}
S
Sandeep Somavarapu 已提交
278

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

293 294
	private clearAll(): void {
		this.rootHandles = [];
S
Sandeep Somavarapu 已提交
295
		this.elements.clear();
296 297
		this.nodes.clear();
	}
298 299 300 301

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