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

import { localize } from 'vs/nls';
import * as vscode from 'vscode';
B
Benjamin Pasero 已提交
8
import { basename } from 'vs/base/common/resources';
9
import { URI } from 'vs/base/common/uri';
J
Joao Moreno 已提交
10
import { Emitter, Event } from 'vs/base/common/event';
11
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
12
import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol';
13
import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions } from 'vs/workbench/common/views';
J
Johannes Rieken 已提交
14
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
15
import { asPromise } from 'vs/base/common/async';
16
import { TreeItemCollapsibleState, ThemeIcon } from 'vs/workbench/api/common/extHostTypes';
S
Sandeep Somavarapu 已提交
17
import { isUndefinedOrNull, isString } from 'vs/base/common/types';
M
Matt Bierner 已提交
18
import { equals, coalesce } from 'vs/base/common/arrays';
S
Sandeep Somavarapu 已提交
19
import { ILogService } from 'vs/platform/log/common/log';
20 21
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
S
Sandeep Somavarapu 已提交
22

S
Sandeep Somavarapu 已提交
23
type TreeItemHandle = string;
S
Sandeep Somavarapu 已提交
24

S
Sandeep Somavarapu 已提交
25
function toTreeItemLabel(label: any, extension: IExtensionDescription): ITreeItemLabel | undefined {
S
Sandeep Somavarapu 已提交
26 27 28 29 30 31 32
	if (isString(label)) {
		return { label };
	}

	if (label
		&& typeof label === 'object'
		&& typeof label.label === 'string') {
33
		checkProposedApiEnabled(extension);
S
Sandeep Somavarapu 已提交
34
		let highlights: [number, number][] | undefined = undefined;
35 36
		if (Array.isArray(label.highlights)) {
			highlights = (<[number, number][]>label.highlights).filter((highlight => highlight.length === 2 && typeof highlight[0] === 'number' && typeof highlight[1] === 'number'));
R
Rob Lourens 已提交
37
			highlights = highlights.length ? highlights : undefined;
S
Sandeep Somavarapu 已提交
38
		}
39
		return { label: label.label, highlights };
S
Sandeep Somavarapu 已提交
40 41
	}

R
Rob Lourens 已提交
42
	return undefined;
S
Sandeep Somavarapu 已提交
43 44 45
}


46
export class ExtHostTreeViews implements ExtHostTreeViewsShape {
S
Sandeep Somavarapu 已提交
47 48 49 50

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

	constructor(
S
Sandeep Somavarapu 已提交
51
		private _proxy: MainThreadTreeViewsShape,
S
Sandeep Somavarapu 已提交
52 53
		private commands: ExtHostCommands,
		private logService: ILogService
S
Sandeep Somavarapu 已提交
54 55 56
	) {
		commands.registerArgumentProcessor({
			processArgument: arg => {
S
Sandeep Somavarapu 已提交
57
				if (arg && arg.$treeViewId && arg.$treeItemHandle) {
S
Sandeep Somavarapu 已提交
58 59 60 61 62 63 64
					return this.convertArgument(arg);
				}
				return arg;
			}
		});
	}

65 66
	registerTreeDataProvider<T>(id: string, treeDataProvider: vscode.TreeDataProvider<T>, extension: IExtensionDescription): vscode.Disposable {
		const treeView = this.createTreeView(id, { treeDataProvider }, extension);
67 68 69
		return { dispose: () => treeView.dispose() };
	}

70
	createTreeView<T>(viewId: string, options: vscode.TreeViewOptions<T>, extension: IExtensionDescription): vscode.TreeView<T> {
71 72 73
		if (!options || !options.treeDataProvider) {
			throw new Error('Options with treeDataProvider is mandatory');
		}
74

S
rename  
Sandeep Somavarapu 已提交
75
		const treeView = this.createExtHostTreeView(viewId, options, extension);
S
Sandeep Somavarapu 已提交
76
		return {
S
Sandeep Somavarapu 已提交
77 78
			get onDidCollapseElement() { return treeView.onDidCollapseElement; },
			get onDidExpandElement() { return treeView.onDidExpandElement; },
79
			get selection() { return treeView.selectedElements; },
80 81 82
			get onDidChangeSelection() { return treeView.onDidChangeSelection; },
			get visible() { return treeView.visible; },
			get onDidChangeVisibility() { return treeView.onDidChangeVisibility; },
83
			get message() { return treeView.message; },
84 85 86 87
			set message(message: string) {
				checkProposedApiEnabled(extension);
				treeView.message = message;
			},
J
Johannes Rieken 已提交
88
			reveal: (element: T, options?: IRevealOptions): Promise<void> => {
S
Sandeep Somavarapu 已提交
89
				return treeView.reveal(element, options);
90
			},
S
Sandeep Somavarapu 已提交
91
			dispose: () => {
92
				this.treeViews.delete(viewId);
S
Sandeep Somavarapu 已提交
93 94 95 96 97
				treeView.dispose();
			}
		};
	}

J
Johannes Rieken 已提交
98
	$getChildren(treeViewId: string, treeItemHandle?: string): Promise<ITreeItem[]> {
S
Sandeep Somavarapu 已提交
99 100
		const treeView = this.treeViews.get(treeViewId);
		if (!treeView) {
101
			return Promise.reject(new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId)));
S
Sandeep Somavarapu 已提交
102 103 104 105
		}
		return treeView.getChildren(treeItemHandle);
	}

106 107
	$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void {
		const treeView = this.treeViews.get(treeViewId);
108 109
		if (!treeView) {
			throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
110
		}
111 112 113 114 115 116 117 118 119
		treeView.setExpanded(treeItemHandle, expanded);
	}

	$setSelection(treeViewId: string, treeItemHandles: string[]): void {
		const treeView = this.treeViews.get(treeViewId);
		if (!treeView) {
			throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
		}
		treeView.setSelection(treeItemHandles);
120 121
	}

122 123 124 125 126 127 128 129
	$setVisible(treeViewId: string, isVisible: boolean): void {
		const treeView = this.treeViews.get(treeViewId);
		if (!treeView) {
			throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
		}
		treeView.setVisible(isVisible);
	}

S
rename  
Sandeep Somavarapu 已提交
130
	private createExtHostTreeView<T>(id: string, options: vscode.TreeViewOptions<T>, extension: IExtensionDescription): ExtHostTreeView<T> {
131
		const treeView = new ExtHostTreeView<T>(id, options, this._proxy, this.commands.converter, this.logService, extension);
132 133 134 135
		this.treeViews.set(id, treeView);
		return treeView;
	}

S
Sandeep Somavarapu 已提交
136 137 138
	private convertArgument(arg: TreeViewItemHandleArg): any {
		const treeView = this.treeViews.get(arg.$treeViewId);
		return treeView ? treeView.getExtensionElement(arg.$treeItemHandle) : null;
S
Sandeep Somavarapu 已提交
139 140 141
	}
}

S
Sandeep Somavarapu 已提交
142 143 144
type Root = null | undefined;
type TreeData<T> = { message: boolean, element: T | Root | false };

145
interface TreeNode extends IDisposable {
146
	item: ITreeItem;
S
Sandeep Somavarapu 已提交
147 148
	parent: TreeNode | Root;
	children?: TreeNode[];
149 150
}

S
Sandeep Somavarapu 已提交
151 152
class ExtHostTreeView<T> extends Disposable {

153
	private static LABEL_HANDLE_PREFIX = '0';
154
	private static ID_HANDLE_PREFIX = '1';
155

156 157
	private readonly dataProvider: vscode.TreeDataProvider<T>;

158
	private roots: TreeNode[] | null = null;
S
Sandeep Somavarapu 已提交
159
	private elements: Map<TreeItemHandle, T> = new Map<TreeItemHandle, T>();
160
	private nodes: Map<T, TreeNode> = new Map<T, TreeNode>();
S
Sandeep Somavarapu 已提交
161

S
Sandeep Somavarapu 已提交
162
	private _visible: boolean = false;
163 164
	get visible(): boolean { return this._visible; }

S
Sandeep Somavarapu 已提交
165
	private _selectedHandles: TreeItemHandle[] = [];
S
Sandeep Somavarapu 已提交
166
	get selectedElements(): T[] { return <T[]>this._selectedHandles.map(handle => this.getExtensionElement(handle)).filter(element => !isUndefinedOrNull(element)); }
167

S
Sandeep Somavarapu 已提交
168 169
	private _onDidExpandElement: Emitter<vscode.TreeViewExpansionEvent<T>> = this._register(new Emitter<vscode.TreeViewExpansionEvent<T>>());
	readonly onDidExpandElement: Event<vscode.TreeViewExpansionEvent<T>> = this._onDidExpandElement.event;
170

S
Sandeep Somavarapu 已提交
171 172
	private _onDidCollapseElement: Emitter<vscode.TreeViewExpansionEvent<T>> = this._register(new Emitter<vscode.TreeViewExpansionEvent<T>>());
	readonly onDidCollapseElement: Event<vscode.TreeViewExpansionEvent<T>> = this._onDidCollapseElement.event;
173

S
Sandeep Somavarapu 已提交
174 175 176
	private _onDidChangeSelection: Emitter<vscode.TreeViewSelectionChangeEvent<T>> = this._register(new Emitter<vscode.TreeViewSelectionChangeEvent<T>>());
	readonly onDidChangeSelection: Event<vscode.TreeViewSelectionChangeEvent<T>> = this._onDidChangeSelection.event;

177 178 179
	private _onDidChangeVisibility: Emitter<vscode.TreeViewVisibilityChangeEvent> = this._register(new Emitter<vscode.TreeViewVisibilityChangeEvent>());
	readonly onDidChangeVisibility: Event<vscode.TreeViewVisibilityChangeEvent> = this._onDidChangeVisibility.event;

180 181
	private _onDidChangeData: Emitter<TreeData<T>> = this._register(new Emitter<TreeData<T>>());

182
	private refreshPromise: Promise<void> = Promise.resolve();
A
Alex Ross 已提交
183
	private refreshQueue: Promise<void> = Promise.resolve();
S
Sandeep Somavarapu 已提交
184

185
	constructor(private viewId: string, options: vscode.TreeViewOptions<T>, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) {
S
Sandeep Somavarapu 已提交
186
		super();
187 188
		this.dataProvider = options.treeDataProvider;
		this.proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll });
189
		if (this.dataProvider.onDidChangeTreeData) {
190 191 192
			this._register(this.dataProvider.onDidChangeTreeData(element => this._onDidChangeData.fire({ message: false, element })));
		}

M
Matt Bierner 已提交
193 194
		let refreshingPromise: Promise<void> | null;
		let promiseCallback: () => void;
S
Sandeep Somavarapu 已提交
195
		this._register(Event.debounce<TreeData<T>, { message: boolean, elements: (T | Root)[] }>(this._onDidChangeData.event, (result, current) => {
196 197 198 199
			if (!result) {
				result = { message: false, elements: [] };
			}
			if (current.element !== false) {
S
Sandeep Somavarapu 已提交
200 201
				if (!refreshingPromise) {
					// New refresh has started
202
					refreshingPromise = new Promise(c => promiseCallback = c);
M
Matt Bierner 已提交
203
					this.refreshPromise = this.refreshPromise.then(() => refreshingPromise!);
S
Sandeep Somavarapu 已提交
204
				}
205 206 207 208 209 210 211 212
				result.elements.push(current.element);
			}
			if (current.message) {
				result.message = true;
			}
			return result;
		}, 200)(({ message, elements }) => {
			if (elements.length) {
A
Alex Ross 已提交
213 214 215 216 217
				this.refreshQueue = this.refreshQueue.then(() => {
					const _promiseCallback = promiseCallback;
					refreshingPromise = null;
					return this.refresh(elements).then(() => _promiseCallback());
				});
218 219 220 221 222
			}
			if (message) {
				this.proxy.$setMessage(this.viewId, this._message);
			}
		}));
S
Sandeep Somavarapu 已提交
223 224
	}

S
Sandeep Somavarapu 已提交
225
	getChildren(parentHandle: TreeItemHandle | Root): Promise<ITreeItem[]> {
R
Rob Lourens 已提交
226
		const parentElement = parentHandle ? this.getExtensionElement(parentHandle) : undefined;
227 228
		if (parentHandle && !parentElement) {
			console.error(`No tree item with id \'${parentHandle}\' found.`);
229
			return Promise.resolve([]);
S
Sandeep Somavarapu 已提交
230 231
		}

232
		const childrenNodes = this.getChildrenNodes(parentHandle); // Get it from cache
233
		return (childrenNodes ? Promise.resolve(childrenNodes) : this.fetchChildrenNodes(parentElement))
234
			.then(nodes => nodes.map(n => n.item));
S
Sandeep Somavarapu 已提交
235 236
	}

S
Sandeep Somavarapu 已提交
237
	getExtensionElement(treeItemHandle: TreeItemHandle): T | undefined {
S
Sandeep Somavarapu 已提交
238
		return this.elements.get(treeItemHandle);
S
Sandeep Somavarapu 已提交
239 240
	}

241
	reveal(element: T, options?: IRevealOptions): Promise<void> {
242 243 244
		options = options ? options : { select: true, focus: false };
		const select = isUndefinedOrNull(options.select) ? true : options.select;
		const focus = isUndefinedOrNull(options.focus) ? false : options.focus;
245
		const expand = isUndefinedOrNull(options.expand) ? false : options.expand;
246

S
Sandeep Somavarapu 已提交
247
		if (typeof this.dataProvider.getParent !== 'function') {
248
			return Promise.reject(new Error(`Required registered TreeDataProvider to implement 'getParent' method to access 'reveal' method`));
S
Sandeep Somavarapu 已提交
249
		}
S
Sandeep Somavarapu 已提交
250 251
		return this.refreshPromise
			.then(() => this.resolveUnknownParentChain(element))
S
Sandeep Somavarapu 已提交
252
			.then(parentChain => this.resolveTreeNode(element, parentChain[parentChain.length - 1])
253
				.then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), { select, focus, expand })), error => this.logService.error(error));
S
Sandeep Somavarapu 已提交
254 255
	}

256 257
	private _message: string = '';
	get message(): string {
258 259 260
		return this._message;
	}

261
	set message(message: string) {
262
		this._message = message;
263
		this._onDidChangeData.fire({ message: true, element: false });
264 265
	}

266 267 268 269
	setExpanded(treeItemHandle: TreeItemHandle, expanded: boolean): void {
		const element = this.getExtensionElement(treeItemHandle);
		if (element) {
			if (expanded) {
270
				this._onDidExpandElement.fire(Object.freeze({ element }));
271
			} else {
272
				this._onDidCollapseElement.fire(Object.freeze({ element }));
273 274 275 276
			}
		}
	}

277
	setSelection(treeItemHandles: TreeItemHandle[]): void {
S
Sandeep Somavarapu 已提交
278 279
		if (!equals(this._selectedHandles, treeItemHandles)) {
			this._selectedHandles = treeItemHandles;
280
			this._onDidChangeSelection.fire(Object.freeze({ selection: this.selectedElements }));
281 282 283 284 285 286
		}
	}

	setVisible(visible: boolean): void {
		if (visible !== this._visible) {
			this._visible = visible;
287
			this._onDidChangeVisibility.fire(Object.freeze({ visible: this._visible }));
S
Sandeep Somavarapu 已提交
288
		}
289 290
	}

J
Johannes Rieken 已提交
291
	private resolveUnknownParentChain(element: T): Promise<TreeNode[]> {
S
Sandeep Somavarapu 已提交
292 293 294
		return this.resolveParent(element)
			.then((parent) => {
				if (!parent) {
295
					return Promise.resolve([]);
S
Sandeep Somavarapu 已提交
296 297
				}
				return this.resolveUnknownParentChain(parent)
S
Sandeep Somavarapu 已提交
298
					.then(result => this.resolveTreeNode(parent, result[result.length - 1])
S
Sandeep Somavarapu 已提交
299 300 301 302 303 304 305
						.then(parentNode => {
							result.push(parentNode);
							return result;
						}));
			});
	}

S
Sandeep Somavarapu 已提交
306
	private resolveParent(element: T): Promise<T | Root> {
S
Sandeep Somavarapu 已提交
307 308
		const node = this.nodes.get(element);
		if (node) {
S
Sandeep Somavarapu 已提交
309
			return Promise.resolve(node.parent ? this.elements.get(node.parent.item.handle) : undefined);
S
Sandeep Somavarapu 已提交
310
		}
S
Sandeep Somavarapu 已提交
311
		return asPromise(() => this.dataProvider.getParent!(element));
S
Sandeep Somavarapu 已提交
312 313
	}

J
Johannes Rieken 已提交
314
	private resolveTreeNode(element: T, parent?: TreeNode): Promise<TreeNode> {
S
Sandeep Somavarapu 已提交
315 316 317 318
		const node = this.nodes.get(element);
		if (node) {
			return Promise.resolve(node);
		}
319
		return asPromise(() => this.dataProvider.getTreeItem(element))
S
Sandeep Somavarapu 已提交
320
			.then(extTreeItem => this.createHandle(element, extTreeItem, parent, true))
S
Sandeep Somavarapu 已提交
321
			.then(handle => this.getChildren(parent ? parent.item.handle : undefined)
S
Sandeep Somavarapu 已提交
322 323 324 325 326
				.then(() => {
					const cachedElement = this.getExtensionElement(handle);
					if (cachedElement) {
						const node = this.nodes.get(cachedElement);
						if (node) {
327
							return Promise.resolve(node);
S
Sandeep Somavarapu 已提交
328 329 330 331 332 333
						}
					}
					throw new Error(`Cannot resolve tree item for element ${handle}`);
				}));
	}

S
Sandeep Somavarapu 已提交
334
	private getChildrenNodes(parentNodeOrHandle: TreeNode | TreeItemHandle | Root): TreeNode[] | null {
335
		if (parentNodeOrHandle) {
S
Sandeep Somavarapu 已提交
336
			let parentNode: TreeNode | undefined;
337 338
			if (typeof parentNodeOrHandle === 'string') {
				const parentElement = this.getExtensionElement(parentNodeOrHandle);
S
Sandeep Somavarapu 已提交
339
				parentNode = parentElement ? this.nodes.get(parentElement) : undefined;
340 341 342
			} else {
				parentNode = parentNodeOrHandle;
			}
S
Sandeep Somavarapu 已提交
343
			return parentNode ? parentNode.children || null : null;
344 345 346 347
		}
		return this.roots;
	}

J
Johannes Rieken 已提交
348
	private fetchChildrenNodes(parentElement?: T): Promise<TreeNode[]> {
349 350 351
		// clear children cache
		this.clearChildren(parentElement);

R
Rob Lourens 已提交
352
		const parentNode = parentElement ? this.nodes.get(parentElement) : undefined;
353
		return asPromise(() => this.dataProvider.getChildren(parentElement))
354
			.then(elements => Promise.all(
M
Matt Bierner 已提交
355
				coalesce(elements || [])
356
					.map(element => asPromise(() => this.dataProvider.getTreeItem(element))
357
						.then(extTreeItem => extTreeItem ? this.createAndRegisterTreeNode(element, extTreeItem, parentNode) : null))))
M
Matt Bierner 已提交
358
			.then(coalesce);
359 360
	}

S
Sandeep Somavarapu 已提交
361
	private refresh(elements: (T | Root)[]): Promise<void> {
362 363
		const hasRoot = elements.some(element => !element);
		if (hasRoot) {
364
			this.clearAll(); // clear cache
S
Sandeep Somavarapu 已提交
365
			return this.proxy.$refresh(this.viewId);
S
Sandeep Somavarapu 已提交
366
		} else {
S
Sandeep Somavarapu 已提交
367
			const handlesToRefresh = this.getHandlesToRefresh(<T[]>elements);
S
Sandeep Somavarapu 已提交
368
			if (handlesToRefresh.length) {
S
Sandeep Somavarapu 已提交
369
				return this.refreshHandles(handlesToRefresh);
370
			}
S
Sandeep Somavarapu 已提交
371
		}
R
Rob Lourens 已提交
372
		return Promise.resolve(undefined);
S
Sandeep Somavarapu 已提交
373 374
	}

375 376 377
	private getHandlesToRefresh(elements: T[]): TreeItemHandle[] {
		const elementsToUpdate = new Set<TreeItemHandle>();
		for (const element of elements) {
378
			const elementNode = this.nodes.get(element);
379
			if (elementNode && !elementsToUpdate.has(elementNode.item.handle)) {
380
				// check if an ancestor of extElement is already in the elements to update list
S
Sandeep Somavarapu 已提交
381
				let currentNode: TreeNode | undefined = elementNode;
382 383
				while (currentNode && currentNode.parent && !elementsToUpdate.has(currentNode.parent.item.handle)) {
					const parentElement = this.elements.get(currentNode.parent.item.handle);
S
Sandeep Somavarapu 已提交
384
					currentNode = parentElement ? this.nodes.get(parentElement) : undefined;
385
				}
S
Sandeep Somavarapu 已提交
386
				if (currentNode && !currentNode.parent) {
387
					elementsToUpdate.add(elementNode.item.handle);
388 389 390 391 392 393 394 395
				}
			}
		}

		const handlesToUpdate: TreeItemHandle[] = [];
		// Take only top level elements
		elementsToUpdate.forEach((handle) => {
			const element = this.elements.get(handle);
S
Sandeep Somavarapu 已提交
396 397 398 399 400
			if (element) {
				const node = this.nodes.get(element);
				if (node && (!node.parent || !elementsToUpdate.has(node.parent.item.handle))) {
					handlesToUpdate.push(handle);
				}
401 402 403 404 405 406
			}
		});

		return handlesToUpdate;
	}

407
	private refreshHandles(itemHandles: TreeItemHandle[]): Promise<void> {
408
		const itemsToRefresh: { [treeItemHandle: string]: ITreeItem } = {};
409
		return Promise.all(itemHandles.map(treeItemHandle =>
410 411 412 413
			this.refreshNode(treeItemHandle)
				.then(node => {
					if (node) {
						itemsToRefresh[treeItemHandle] = node.item;
414
					}
415
				})))
S
Sandeep Somavarapu 已提交
416
			.then(() => Object.keys(itemsToRefresh).length ? this.proxy.$refresh(this.viewId, itemsToRefresh) : undefined);
S
Sandeep Somavarapu 已提交
417 418
	}

S
Sandeep Somavarapu 已提交
419
	private refreshNode(treeItemHandle: TreeItemHandle): Promise<TreeNode | null> {
420
		const extElement = this.getExtensionElement(treeItemHandle);
S
Sandeep Somavarapu 已提交
421 422 423 424 425 426 427 428 429
		if (extElement) {
			const existing = this.nodes.get(extElement);
			if (existing) {
				this.clearChildren(extElement); // clear children cache
				return asPromise(() => this.dataProvider.getTreeItem(extElement))
					.then(extTreeItem => {
						if (extTreeItem) {
							const newNode = this.createTreeNode(extElement, extTreeItem, existing.parent);
							this.updateNodeCache(extElement, newNode, existing, existing.parent);
430
							existing.dispose();
S
Sandeep Somavarapu 已提交
431 432 433 434 435 436 437
							return newNode;
						}
						return null;
					});
			}
		}
		return Promise.resolve(null);
438
	}
S
Sandeep Somavarapu 已提交
439

S
Sandeep Somavarapu 已提交
440
	private createAndRegisterTreeNode(element: T, extTreeItem: vscode.TreeItem, parentNode: TreeNode | Root): TreeNode {
441 442 443 444 445 446 447 448
		const node = this.createTreeNode(element, extTreeItem, parentNode);
		if (extTreeItem.id && this.elements.has(node.item.handle)) {
			throw new Error(localize('treeView.duplicateElement', 'Element with id {0} is already registered', extTreeItem.id));
		}
		this.addNodeToCache(element, node);
		this.addNodeToParentCache(node, parentNode);
		return node;
	}
S
Sandeep Somavarapu 已提交
449

S
Sandeep Somavarapu 已提交
450
	private createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): TreeNode {
451
		const disposable = new DisposableStore();
452 453 454
		const handle = this.createHandle(element, extensionTreeItem, parent);
		const icon = this.getLightIconPath(extensionTreeItem);
		const item = {
455
			handle,
R
Rob Lourens 已提交
456
			parentHandle: parent ? parent.item.handle : undefined,
457
			label: toTreeItemLabel(extensionTreeItem.label, this.extension),
458
			description: extensionTreeItem.description,
S
Sandeep Somavarapu 已提交
459
			resourceUri: extensionTreeItem.resourceUri,
R
Rob Lourens 已提交
460
			tooltip: typeof extensionTreeItem.tooltip === 'string' ? extensionTreeItem.tooltip : undefined,
461
			command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command, disposable) : undefined,
S
Sandeep Somavarapu 已提交
462
			contextValue: extensionTreeItem.contextValue,
463 464
			icon,
			iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
R
Rob Lourens 已提交
465
			themeIcon: extensionTreeItem.iconPath instanceof ThemeIcon ? { id: extensionTreeItem.iconPath.id } : undefined,
S
Sandeep Somavarapu 已提交
466
			collapsibleState: isUndefinedOrNull(extensionTreeItem.collapsibleState) ? TreeItemCollapsibleState.None : extensionTreeItem.collapsibleState
S
Sandeep Somavarapu 已提交
467
		};
468

469 470 471 472
		return {
			item,
			parent,
			children: undefined,
473
			dispose(): void { disposable.dispose(); }
474
		};
S
Sandeep Somavarapu 已提交
475 476
	}

S
Sandeep Somavarapu 已提交
477
	private createHandle(element: T, { id, label, resourceUri }: vscode.TreeItem, parent: TreeNode | Root, returnFirst?: boolean): TreeItemHandle {
S
Sandeep Somavarapu 已提交
478 479
		if (id) {
			return `${ExtHostTreeView.ID_HANDLE_PREFIX}/${id}`;
480 481
		}

482
		const treeItemLabel = toTreeItemLabel(label, this.extension);
483
		const prefix: string = parent ? parent.item.handle : ExtHostTreeView.LABEL_HANDLE_PREFIX;
B
Benjamin Pasero 已提交
484
		let elementId = treeItemLabel ? treeItemLabel.label : resourceUri ? basename(resourceUri) : '';
S
Sandeep Somavarapu 已提交
485
		elementId = elementId.indexOf('/') !== -1 ? elementId.replace('/', '//') : elementId;
S
Sandeep Somavarapu 已提交
486
		const existingHandle = this.nodes.has(element) ? this.nodes.get(element)!.item.handle : undefined;
487
		const childrenNodes = (this.getChildrenNodes(parent) || []);
488

S
Sandeep Somavarapu 已提交
489 490 491 492 493 494 495 496 497
		let handle: TreeItemHandle;
		let counter = 0;
		do {
			handle = `${prefix}/${counter}:${elementId}`;
			if (returnFirst || !this.elements.has(handle) || existingHandle === handle) {
				// Return first if asked for or
				// Return if handle does not exist or
				// Return if handle is being reused
				break;
498
			}
S
Sandeep Somavarapu 已提交
499 500
			counter++;
		} while (counter <= childrenNodes.length);
501

S
Sandeep Somavarapu 已提交
502
		return handle;
S
Sandeep Somavarapu 已提交
503 504
	}

S
Sandeep Somavarapu 已提交
505
	private getLightIconPath(extensionTreeItem: vscode.TreeItem): URI | undefined {
S
Sandeep Somavarapu 已提交
506
		if (extensionTreeItem.iconPath && !(extensionTreeItem.iconPath instanceof ThemeIcon)) {
507
			if (typeof extensionTreeItem.iconPath === 'string'
S
Sandeep Somavarapu 已提交
508
				|| extensionTreeItem.iconPath instanceof URI) {
S
Sandeep Somavarapu 已提交
509 510
				return this.getIconPath(extensionTreeItem.iconPath);
			}
S
Sandeep Somavarapu 已提交
511
			return this.getIconPath((<{ light: string | URI; dark: string | URI }>extensionTreeItem.iconPath).light);
S
Sandeep Somavarapu 已提交
512
		}
R
Rob Lourens 已提交
513
		return undefined;
S
Sandeep Somavarapu 已提交
514 515
	}

S
Sandeep Somavarapu 已提交
516
	private getDarkIconPath(extensionTreeItem: vscode.TreeItem): URI | undefined {
S
Sandeep Somavarapu 已提交
517 518
		if (extensionTreeItem.iconPath && !(extensionTreeItem.iconPath instanceof ThemeIcon) && (<{ light: string | URI; dark: string | URI }>extensionTreeItem.iconPath).dark) {
			return this.getIconPath((<{ light: string | URI; dark: string | URI }>extensionTreeItem.iconPath).dark);
S
Sandeep Somavarapu 已提交
519
		}
R
Rob Lourens 已提交
520
		return undefined;
S
Sandeep Somavarapu 已提交
521 522
	}

523
	private getIconPath(iconPath: string | URI): URI {
S
Sandeep Somavarapu 已提交
524
		if (iconPath instanceof URI) {
525
			return iconPath;
S
Sandeep Somavarapu 已提交
526
		}
527
		return URI.file(iconPath);
S
Sandeep Somavarapu 已提交
528 529
	}

530 531 532
	private addNodeToCache(element: T, node: TreeNode): void {
		this.elements.set(node.item.handle, element);
		this.nodes.set(element, node);
533
	}
534

S
Sandeep Somavarapu 已提交
535
	private updateNodeCache(element: T, newNode: TreeNode, existing: TreeNode, parentNode: TreeNode | Root): void {
536 537 538 539 540 541
		// Remove from the cache
		this.elements.delete(newNode.item.handle);
		this.nodes.delete(element);
		if (newNode.item.handle !== existing.item.handle) {
			this.elements.delete(existing.item.handle);
		}
542

543 544
		// Add the new node to the cache
		this.addNodeToCache(element, newNode);
545

546 547 548 549 550 551 552
		// Replace the node in parent's children nodes
		const childrenNodes = (this.getChildrenNodes(parentNode) || []);
		const childNode = childrenNodes.filter(c => c.item.handle === existing.item.handle)[0];
		if (childNode) {
			childrenNodes.splice(childrenNodes.indexOf(childNode), 1, newNode);
		}
	}
553

S
Sandeep Somavarapu 已提交
554
	private addNodeToParentCache(node: TreeNode, parentNode: TreeNode | Root): void {
555 556 557
		if (parentNode) {
			if (!parentNode.children) {
				parentNode.children = [];
558
			}
559
			parentNode.children.push(node);
560
		} else {
561 562 563 564
			if (!this.roots) {
				this.roots = [];
			}
			this.roots.push(node);
565
		}
S
Sandeep Somavarapu 已提交
566 567
	}

568
	private clearChildren(parentElement?: T): void {
S
Sandeep Somavarapu 已提交
569
		if (parentElement) {
570
			const node = this.nodes.get(parentElement);
S
Sandeep Somavarapu 已提交
571 572 573
			if (node) {
				if (node.children) {
					for (const child of node.children) {
574 575 576
						const childElement = this.elements.get(child.item.handle);
						if (childElement) {
							this.clear(childElement);
S
Sandeep Somavarapu 已提交
577
						}
578
					}
S
Sandeep Somavarapu 已提交
579
				}
S
Sandeep Somavarapu 已提交
580
				node.children = undefined;
581
			}
582 583
		} else {
			this.clearAll();
584 585
		}
	}
S
Sandeep Somavarapu 已提交
586

587
	private clear(element: T): void {
588
		const node = this.nodes.get(element);
S
Sandeep Somavarapu 已提交
589 590 591
		if (node) {
			if (node.children) {
				for (const child of node.children) {
592 593 594
					const childElement = this.elements.get(child.item.handle);
					if (childElement) {
						this.clear(childElement);
S
Sandeep Somavarapu 已提交
595
					}
596 597
				}
			}
S
Sandeep Somavarapu 已提交
598 599
			this.nodes.delete(element);
			this.elements.delete(node.item.handle);
600
			node.dispose();
S
Sandeep Somavarapu 已提交
601 602 603
		}
	}

604
	private clearAll(): void {
605
		this.roots = null;
S
Sandeep Somavarapu 已提交
606
		this.elements.clear();
607
		this.nodes.forEach(node => node.dispose());
608 609
		this.nodes.clear();
	}
610 611 612 613

	dispose() {
		this.clearAll();
	}
614
}