extHostTreeViews.ts 22.5 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';
S
Sandeep Somavarapu 已提交
11
import { Disposable } 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';
J
Johannes Rieken 已提交
16
import { TreeItemCollapsibleState, ThemeIcon, MarkdownString } 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
			set message(message: string | MarkdownString) { checkProposedApiEnabled(extension); treeView.message = message; },
J
Johannes Rieken 已提交
85
			reveal: (element: T, options?: IRevealOptions): Promise<void> => {
S
Sandeep Somavarapu 已提交
86
				return treeView.reveal(element, options);
87
			},
S
Sandeep Somavarapu 已提交
88
			dispose: () => {
89
				this.treeViews.delete(viewId);
S
Sandeep Somavarapu 已提交
90 91 92 93 94
				treeView.dispose();
			}
		};
	}

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

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

119 120 121 122 123 124 125 126
	$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 已提交
127
	private createExtHostTreeView<T>(id: string, options: vscode.TreeViewOptions<T>, extension: IExtensionDescription): ExtHostTreeView<T> {
128
		const treeView = new ExtHostTreeView<T>(id, options, this._proxy, this.commands.converter, this.logService, extension);
129 130 131 132
		this.treeViews.set(id, treeView);
		return treeView;
	}

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

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

142
interface TreeNode {
143
	item: ITreeItem;
S
Sandeep Somavarapu 已提交
144 145
	parent: TreeNode | Root;
	children?: TreeNode[];
146 147
}

S
Sandeep Somavarapu 已提交
148 149
class ExtHostTreeView<T> extends Disposable {

150
	private static LABEL_HANDLE_PREFIX = '0';
151
	private static ID_HANDLE_PREFIX = '1';
152

153 154
	private readonly dataProvider: vscode.TreeDataProvider<T>;

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

S
Sandeep Somavarapu 已提交
159
	private _visible: boolean = false;
160 161
	get visible(): boolean { return this._visible; }

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

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

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

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

174 175 176
	private _onDidChangeVisibility: Emitter<vscode.TreeViewVisibilityChangeEvent> = this._register(new Emitter<vscode.TreeViewVisibilityChangeEvent>());
	readonly onDidChangeVisibility: Event<vscode.TreeViewVisibilityChangeEvent> = this._onDidChangeVisibility.event;

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

179
	private refreshPromise: Promise<void> = Promise.resolve();
S
Sandeep Somavarapu 已提交
180

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

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

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

226
		const childrenNodes = this.getChildrenNodes(parentHandle); // Get it from cache
227
		return (childrenNodes ? Promise.resolve(childrenNodes) : this.fetchChildrenNodes(parentElement))
228
			.then(nodes => nodes.map(n => n.item));
S
Sandeep Somavarapu 已提交
229 230
	}

S
Sandeep Somavarapu 已提交
231
	getExtensionElement(treeItemHandle: TreeItemHandle): T | undefined {
S
Sandeep Somavarapu 已提交
232
		return this.elements.get(treeItemHandle);
S
Sandeep Somavarapu 已提交
233 234
	}

235
	reveal(element: T, options?: IRevealOptions): Promise<void> {
236 237 238
		options = options ? options : { select: true, focus: false };
		const select = isUndefinedOrNull(options.select) ? true : options.select;
		const focus = isUndefinedOrNull(options.focus) ? false : options.focus;
239
		const expand = isUndefinedOrNull(options.expand) ? false : options.expand;
240

S
Sandeep Somavarapu 已提交
241
		if (typeof this.dataProvider.getParent !== 'function') {
242
			return Promise.reject(new Error(`Required registered TreeDataProvider to implement 'getParent' method to access 'reveal' method`));
S
Sandeep Somavarapu 已提交
243
		}
S
Sandeep Somavarapu 已提交
244 245
		return this.refreshPromise
			.then(() => this.resolveUnknownParentChain(element))
S
Sandeep Somavarapu 已提交
246
			.then(parentChain => this.resolveTreeNode(element, parentChain[parentChain.length - 1])
247
				.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 已提交
248 249
	}

250 251 252 253 254 255 256
	private _message: string | MarkdownString;
	get message(): string | MarkdownString {
		return this._message;
	}

	set message(message: string | MarkdownString) {
		this._message = message;
257
		this._onDidChangeData.fire({ message: true, element: false });
258 259
	}

260 261 262 263
	setExpanded(treeItemHandle: TreeItemHandle, expanded: boolean): void {
		const element = this.getExtensionElement(treeItemHandle);
		if (element) {
			if (expanded) {
264
				this._onDidExpandElement.fire(Object.freeze({ element }));
265
			} else {
266
				this._onDidCollapseElement.fire(Object.freeze({ element }));
267 268 269 270
			}
		}
	}

271
	setSelection(treeItemHandles: TreeItemHandle[]): void {
S
Sandeep Somavarapu 已提交
272 273
		if (!equals(this._selectedHandles, treeItemHandles)) {
			this._selectedHandles = treeItemHandles;
274
			this._onDidChangeSelection.fire(Object.freeze({ selection: this.selectedElements }));
275 276 277 278 279 280
		}
	}

	setVisible(visible: boolean): void {
		if (visible !== this._visible) {
			this._visible = visible;
281
			this._onDidChangeVisibility.fire(Object.freeze({ visible: this._visible }));
S
Sandeep Somavarapu 已提交
282
		}
283 284
	}

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

S
Sandeep Somavarapu 已提交
300
	private resolveParent(element: T): Promise<T | Root> {
S
Sandeep Somavarapu 已提交
301 302
		const node = this.nodes.get(element);
		if (node) {
S
Sandeep Somavarapu 已提交
303
			return Promise.resolve(node.parent ? this.elements.get(node.parent.item.handle) : undefined);
S
Sandeep Somavarapu 已提交
304
		}
S
Sandeep Somavarapu 已提交
305
		return asPromise(() => this.dataProvider.getParent!(element));
S
Sandeep Somavarapu 已提交
306 307
	}

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

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

J
Johannes Rieken 已提交
342
	private fetchChildrenNodes(parentElement?: T): Promise<TreeNode[]> {
343 344 345
		// clear children cache
		this.clearChildren(parentElement);

R
Rob Lourens 已提交
346
		const parentNode = parentElement ? this.nodes.get(parentElement) : undefined;
347
		return asPromise(() => this.dataProvider.getChildren(parentElement))
348
			.then(elements => Promise.all(
M
Matt Bierner 已提交
349
				coalesce(elements || [])
350
					.map(element => asPromise(() => this.dataProvider.getTreeItem(element))
351
						.then(extTreeItem => extTreeItem ? this.createAndRegisterTreeNode(element, extTreeItem, parentNode) : null))))
M
Matt Bierner 已提交
352
			.then(coalesce);
353 354
	}

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

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

		const handlesToUpdate: TreeItemHandle[] = [];
		// Take only top level elements
		elementsToUpdate.forEach((handle) => {
			const element = this.elements.get(handle);
S
Sandeep Somavarapu 已提交
390 391 392 393 394
			if (element) {
				const node = this.nodes.get(element);
				if (node && (!node.parent || !elementsToUpdate.has(node.parent.item.handle))) {
					handlesToUpdate.push(handle);
				}
395 396 397 398 399 400
			}
		});

		return handlesToUpdate;
	}

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

S
Sandeep Somavarapu 已提交
413
	private refreshNode(treeItemHandle: TreeItemHandle): Promise<TreeNode | null> {
414
		const extElement = this.getExtensionElement(treeItemHandle);
S
Sandeep Somavarapu 已提交
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
		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);
							return newNode;
						}
						return null;
					});
			}
		}
		return Promise.resolve(null);
431
	}
S
Sandeep Somavarapu 已提交
432

S
Sandeep Somavarapu 已提交
433
	private createAndRegisterTreeNode(element: T, extTreeItem: vscode.TreeItem, parentNode: TreeNode | Root): TreeNode {
434 435 436 437 438 439 440 441
		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 已提交
442

S
Sandeep Somavarapu 已提交
443
	private createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): TreeNode {
S
Sandeep Somavarapu 已提交
444
		return {
445 446
			item: this.createTreeItem(element, extensionTreeItem, parent),
			parent,
R
Rob Lourens 已提交
447
			children: undefined
448 449 450
		};
	}

S
Sandeep Somavarapu 已提交
451
	private createTreeItem(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): ITreeItem {
452 453 454 455

		const handle = this.createHandle(element, extensionTreeItem, parent);
		const icon = this.getLightIconPath(extensionTreeItem);
		const item = {
456
			handle,
R
Rob Lourens 已提交
457
			parentHandle: parent ? parent.item.handle : undefined,
458
			label: toTreeItemLabel(extensionTreeItem.label, this.extension),
459
			description: extensionTreeItem.description,
S
Sandeep Somavarapu 已提交
460
			resourceUri: extensionTreeItem.resourceUri,
R
Rob Lourens 已提交
461 462
			tooltip: typeof extensionTreeItem.tooltip === 'string' ? extensionTreeItem.tooltip : undefined,
			command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command) : undefined,
S
Sandeep Somavarapu 已提交
463
			contextValue: extensionTreeItem.contextValue,
464 465
			icon,
			iconDark: this.getDarkIconPath(extensionTreeItem) || icon,
R
Rob Lourens 已提交
466
			themeIcon: extensionTreeItem.iconPath instanceof ThemeIcon ? { id: extensionTreeItem.iconPath.id } : undefined,
S
Sandeep Somavarapu 已提交
467
			collapsibleState: isUndefinedOrNull(extensionTreeItem.collapsibleState) ? TreeItemCollapsibleState.None : extensionTreeItem.collapsibleState
S
Sandeep Somavarapu 已提交
468
		};
469 470

		return item;
S
Sandeep Somavarapu 已提交
471 472
	}

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

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

S
Sandeep Somavarapu 已提交
485 486 487 488 489 490 491 492 493
		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;
494
			}
S
Sandeep Somavarapu 已提交
495 496
			counter++;
		} while (counter <= childrenNodes.length);
497

S
Sandeep Somavarapu 已提交
498
		return handle;
S
Sandeep Somavarapu 已提交
499 500
	}

S
Sandeep Somavarapu 已提交
501
	private getLightIconPath(extensionTreeItem: vscode.TreeItem): URI | undefined {
S
Sandeep Somavarapu 已提交
502
		if (extensionTreeItem.iconPath && !(extensionTreeItem.iconPath instanceof ThemeIcon)) {
503
			if (typeof extensionTreeItem.iconPath === 'string'
S
Sandeep Somavarapu 已提交
504
				|| extensionTreeItem.iconPath instanceof URI) {
S
Sandeep Somavarapu 已提交
505 506 507 508
				return this.getIconPath(extensionTreeItem.iconPath);
			}
			return this.getIconPath(extensionTreeItem.iconPath['light']);
		}
R
Rob Lourens 已提交
509
		return undefined;
S
Sandeep Somavarapu 已提交
510 511
	}

S
Sandeep Somavarapu 已提交
512
	private getDarkIconPath(extensionTreeItem: vscode.TreeItem): URI | undefined {
S
Sandeep Somavarapu 已提交
513
		if (extensionTreeItem.iconPath && !(extensionTreeItem.iconPath instanceof ThemeIcon) && extensionTreeItem.iconPath['dark']) {
S
Sandeep Somavarapu 已提交
514 515
			return this.getIconPath(extensionTreeItem.iconPath['dark']);
		}
R
Rob Lourens 已提交
516
		return undefined;
S
Sandeep Somavarapu 已提交
517 518
	}

519
	private getIconPath(iconPath: string | URI): URI {
S
Sandeep Somavarapu 已提交
520
		if (iconPath instanceof URI) {
521
			return iconPath;
S
Sandeep Somavarapu 已提交
522
		}
523
		return URI.file(iconPath);
S
Sandeep Somavarapu 已提交
524 525
	}

526 527 528
	private addNodeToCache(element: T, node: TreeNode): void {
		this.elements.set(node.item.handle, element);
		this.nodes.set(element, node);
529
	}
530

S
Sandeep Somavarapu 已提交
531
	private updateNodeCache(element: T, newNode: TreeNode, existing: TreeNode, parentNode: TreeNode | Root): void {
532 533 534 535 536 537
		// 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);
		}
538

539 540
		// Add the new node to the cache
		this.addNodeToCache(element, newNode);
541

542 543 544 545 546 547 548
		// 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);
		}
	}
549

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

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

583
	private clear(element: T): void {
584
		const node = this.nodes.get(element);
S
Sandeep Somavarapu 已提交
585 586 587 588 589 590 591
		if (node) {
			if (node.children) {
				for (const child of node.children) {
					const childEleement = this.elements.get(child.item.handle);
					if (childEleement) {
						this.clear(childEleement);
					}
592 593
				}
			}
S
Sandeep Somavarapu 已提交
594 595
			this.nodes.delete(element);
			this.elements.delete(node.item.handle);
S
Sandeep Somavarapu 已提交
596 597 598
		}
	}

599
	private clearAll(): void {
600
		this.roots = null;
S
Sandeep Somavarapu 已提交
601
		this.elements.clear();
602 603
		this.nodes.clear();
	}
604 605 606 607

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