indexTreeModel.ts 13.8 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

J
Joao Moreno 已提交
6
import { ISpliceable } from 'vs/base/common/sequence';
J
Joao Moreno 已提交
7
import { Iterator, ISequence } from 'vs/base/common/iterator';
J
Joao Moreno 已提交
8
import { Emitter, Event, EventBufferer } from 'vs/base/common/event';
J
Joao Moreno 已提交
9
import { tail2 } from 'vs/base/common/arrays';
J
Joao Moreno 已提交
10
import { ITreeFilterDataResult, TreeVisibility, ITreeFilter, ITreeOptions, ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree';
J
Joao Moreno 已提交
11

J
Joao Moreno 已提交
12
interface IMutableTreeNode<T, TFilterData> extends ITreeNode<T, TFilterData> {
13 14
	readonly parent: IMutableTreeNode<T, TFilterData> | undefined;
	readonly children: IMutableTreeNode<T, TFilterData>[];
15
	collapsible: boolean;
J
Joao Moreno 已提交
16
	collapsed: boolean;
J
Joao Moreno 已提交
17
	renderNodeCount: number;
J
Joao Moreno 已提交
18
	visible: boolean;
J
Joao Moreno 已提交
19
	filterData: TFilterData | undefined;
J
Joao Moreno 已提交
20
}
J
Joao Moreno 已提交
21

J
Joao Moreno 已提交
22
function isFilterResult<T>(obj: any): obj is ITreeFilterDataResult<T> {
23 24 25
	return typeof obj === 'object' && 'visibility' in obj && 'data' in obj;
}

J
Joao Moreno 已提交
26
function treeNodeToElement<T>(node: IMutableTreeNode<T, any>): ITreeElement<T> {
J
Joao Moreno 已提交
27
	const { element, collapsed } = node;
J
Joao Moreno 已提交
28
	const children = Iterator.map(Iterator.fromArray(node.children), treeNodeToElement);
J
Joao Moreno 已提交
29 30

	return { element, children, collapsed };
J
Joao Moreno 已提交
31 32
}

J
Joao Moreno 已提交
33
function getVisibleState(visibility: boolean | TreeVisibility): boolean | undefined {
J
Joao Moreno 已提交
34
	switch (visibility) {
J
Joao Moreno 已提交
35 36
		case true: return true;
		case false: return false;
J
Joao Moreno 已提交
37 38 39
		case TreeVisibility.Hidden: return false;
		case TreeVisibility.Visible: return true;
		case TreeVisibility.Recurse: return undefined;
J
Joao Moreno 已提交
40 41 42
	}
}

J
Joao Moreno 已提交
43
export interface IIndexTreeModelOptions<T, TFilterData> extends ITreeOptions<T, TFilterData> { }
J
Joao Moreno 已提交
44

J
Joao Moreno 已提交
45
export class IndexTreeModel<T, TFilterData = void> implements ITreeModel<T, TFilterData, number[]> {
J
Joao Moreno 已提交
46

J
Joao Moreno 已提交
47
	private root: IMutableTreeNode<T, TFilterData> = {
J
Joao Moreno 已提交
48
		parent: undefined,
J
Joao Moreno 已提交
49 50 51
		element: undefined,
		children: [],
		depth: 0,
J
Joao Moreno 已提交
52
		collapsible: false,
J
Joao Moreno 已提交
53
		collapsed: false,
J
Joao Moreno 已提交
54
		renderNodeCount: 0,
J
Joao Moreno 已提交
55 56
		visible: true,
		filterData: undefined
J
Joao Moreno 已提交
57
	};
58

J
Joao Moreno 已提交
59 60
	private eventBufferer = new EventBufferer();

J
Joao Moreno 已提交
61
	private _onDidChangeCollapseState = new Emitter<ITreeNode<T, TFilterData>>();
J
Joao Moreno 已提交
62 63 64 65
	readonly onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>> = this.eventBufferer.wrapEvent(this._onDidChangeCollapseState.event);

	private _onDidChangeRenderNodeCount = new Emitter<ITreeNode<T, TFilterData>>();
	readonly onDidChangeRenderNodeCount: Event<ITreeNode<T, TFilterData>> = this.eventBufferer.wrapEvent(this._onDidChangeRenderNodeCount.event);
66

J
Joao Moreno 已提交
67
	private filter?: ITreeFilter<T, TFilterData>;
68

J
Joao Moreno 已提交
69
	constructor(private list: ISpliceable<ITreeNode<T, TFilterData>>, options: IIndexTreeModelOptions<T, TFilterData> = {}) {
70 71
		this.filter = options.filter;
	}
J
Joao Moreno 已提交
72

J
Joao Moreno 已提交
73 74 75 76 77 78 79
	splice(
		location: number[],
		deleteCount: number,
		toInsert?: ISequence<ITreeElement<T>>,
		onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void,
		onDidDeleteNode?: (node: ITreeNode<T, TFilterData>) => void
	): Iterator<ITreeElement<T>> {
J
Joao Moreno 已提交
80
		if (location.length === 0) {
J
Joao Moreno 已提交
81 82 83
			throw new Error('Invalid tree location');
		}

J
Joao Moreno 已提交
84
		const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location);
J
Joao Moreno 已提交
85
		const treeListElementsToInsert: ITreeNode<T, TFilterData>[] = [];
J
Joao Moreno 已提交
86
		const nodesToInsertIterator = Iterator.map(Iterator.from(toInsert), el => this.createTreeNode(el, parentNode, revealed, treeListElementsToInsert, onDidCreateNode));
J
Joao Moreno 已提交
87 88

		const nodesToInsert: IMutableTreeNode<T, TFilterData>[] = [];
J
Joao Moreno 已提交
89
		let renderNodeCount = 0;
J
Joao Moreno 已提交
90 91 92

		Iterator.forEach(nodesToInsertIterator, node => {
			nodesToInsert.push(node);
J
Joao Moreno 已提交
93
			renderNodeCount += node.renderNodeCount;
J
Joao Moreno 已提交
94 95
		});

J
Joao Moreno 已提交
96 97
		const lastIndex = location[location.length - 1];
		const deletedNodes = parentNode.children.splice(lastIndex, deleteCount, ...nodesToInsert);
J
Joao Moreno 已提交
98

99
		if (revealed) {
J
Joao Moreno 已提交
100
			const visibleDeleteCount = deletedNodes.reduce((r, node) => r + node.renderNodeCount, 0);
101

J
Joao Moreno 已提交
102
			this._updateAncestorsRenderNodeCount(parentNode, renderNodeCount - visibleDeleteCount);
103
			this.list.splice(listIndex, visibleDeleteCount, treeListElementsToInsert);
J
Joao Moreno 已提交
104
		}
J
Joao Moreno 已提交
105

106
		if (deletedNodes.length > 0 && onDidDeleteNode) {
J
Joao Moreno 已提交
107 108 109 110 111 112 113 114
			const visit = (node: ITreeNode<T, TFilterData>) => {
				onDidDeleteNode(node);
				node.children.forEach(visit);
			};

			deletedNodes.forEach(visit);
		}

J
Joao Moreno 已提交
115
		return Iterator.map(Iterator.fromArray(deletedNodes), treeNodeToElement);
J
Joao Moreno 已提交
116 117
	}

J
Joao Moreno 已提交
118
	getListIndex(location: number[]): number {
J
Joao Moreno 已提交
119
		return this.getNodeWithListIndex(location).listIndex;
J
Joao Moreno 已提交
120 121
	}

J
Joao Moreno 已提交
122
	setCollapsed(location: number[], collapsed: boolean): boolean {
J
Joao Moreno 已提交
123
		const { node, listIndex, revealed } = this.getNodeWithListIndex(location);
J
Joao Moreno 已提交
124
		return this.eventBufferer.bufferEvents(() => this._setCollapsed(node, listIndex, revealed, collapsed));
J
Joao Moreno 已提交
125 126 127
	}

	toggleCollapsed(location: number[]): void {
J
Joao Moreno 已提交
128
		const { node, listIndex, revealed } = this.getNodeWithListIndex(location);
J
Joao Moreno 已提交
129
		this.eventBufferer.bufferEvents(() => this._setCollapsed(node, listIndex, revealed));
J
Joao Moreno 已提交
130
	}
J
Joao Moreno 已提交
131

J
Joao Moreno 已提交
132 133 134 135 136
	// // TODO@joao cleanup
	// setCollapsedAll(collapsed: boolean): void {
	// 	if (collapsed) {
	// 		const queue = [...this.root.children]; // TODO@joao use a linked list
	// 		let listIndex = 0;
137

J
Joao Moreno 已提交
138 139 140 141
	// 		while (queue.length > 0) {
	// 			const node = queue.shift();
	// 			const revealed = listIndex < this.root.children.length;
	// 			this._setCollapsed(node, listIndex, revealed, collapsed);
142

J
Joao Moreno 已提交
143 144 145 146 147
	// 			queue.push(...node.children);
	// 			listIndex++;
	// 		}
	// 	}
	// }
148 149

	isCollapsed(location: number[]): boolean {
J
Joao Moreno 已提交
150
		return this.getNode(location).collapsed;
151 152
	}

J
Joao Moreno 已提交
153
	refilter(): void {
J
Joao Moreno 已提交
154
		const previousRenderNodeCount = this.root.renderNodeCount;
J
Joao Moreno 已提交
155
		const toInsert = this.updateNodeAfterFilterChange(this.root);
J
Joao Moreno 已提交
156
		this.list.splice(0, previousRenderNodeCount, toInsert);
J
Joao Moreno 已提交
157 158
	}

159
	private _setCollapsed(node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean, collapsed?: boolean | undefined): boolean {
J
Joao Moreno 已提交
160 161 162 163
		if (!node.collapsible) {
			return false;
		}

J
Joao Moreno 已提交
164 165 166 167
		if (typeof collapsed === 'undefined') {
			collapsed = !node.collapsed;
		}

J
Joao Moreno 已提交
168
		if (node.collapsed === collapsed) {
J
Joao Moreno 已提交
169
			return false;
J
Joao Moreno 已提交
170 171 172 173
		}

		node.collapsed = collapsed;

174
		if (revealed) {
J
Joao Moreno 已提交
175
			const previousRenderNodeCount = node.renderNodeCount;
J
Joao Moreno 已提交
176
			const toInsert = this.updateNodeAfterCollapseChange(node);
177

J
Joao Moreno 已提交
178
			this.list.splice(listIndex + 1, previousRenderNodeCount - 1, toInsert.slice(1));
J
Joao Moreno 已提交
179
			this._onDidChangeCollapseState.fire(node);
J
Joao Moreno 已提交
180
		}
J
Joao Moreno 已提交
181 182

		return true;
J
Joao Moreno 已提交
183 184
	}

J
Joao Moreno 已提交
185 186 187 188 189 190 191
	private createTreeNode(
		treeElement: ITreeElement<T>,
		parent: IMutableTreeNode<T, TFilterData>,
		revealed: boolean,
		treeListElements: ITreeNode<T, TFilterData>[],
		onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void
	): IMutableTreeNode<T, TFilterData> {
J
Joao Moreno 已提交
192 193 194 195 196
		const node: IMutableTreeNode<T, TFilterData> = {
			parent,
			element: treeElement.element,
			children: [],
			depth: parent.depth + 1,
197
			collapsible: typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : (typeof treeElement.collapsed === 'boolean'),
J
Joao Moreno 已提交
198
			collapsed: !!treeElement.collapsed,
J
Joao Moreno 已提交
199
			renderNodeCount: 1,
J
Joao Moreno 已提交
200 201 202
			visible: true,
			filterData: undefined
		};
J
Joao Moreno 已提交
203

J
Joao Moreno 已提交
204
		const visible = this._filterNode(node);
J
Joao Moreno 已提交
205

J
Joao Moreno 已提交
206
		if (revealed) {
207 208 209
			treeListElements.push(node);
		}

J
Joao Moreno 已提交
210
		const childElements = Iterator.from(treeElement.children);
J
Joao Moreno 已提交
211 212
		const childRevealed = revealed && visible !== false && !node.collapsed;
		const childNodes = Iterator.map(childElements, el => this.createTreeNode(el, node, childRevealed, treeListElements, onDidCreateNode));
J
Joao Moreno 已提交
213

214
		let hasVisibleDescendants = false;
J
Joao Moreno 已提交
215
		let renderNodeCount = 1;
216 217 218 219

		Iterator.forEach(childNodes, child => {
			node.children.push(child);
			hasVisibleDescendants = hasVisibleDescendants || child.visible;
J
Joao Moreno 已提交
220
			renderNodeCount += child.renderNodeCount;
221 222
		});

223
		node.collapsible = node.collapsible || node.children.length > 0;
J
Joao Moreno 已提交
224
		node.visible = typeof visible === 'undefined' ? hasVisibleDescendants : visible;
J
Joao Moreno 已提交
225

J
Joao Moreno 已提交
226
		if (!node.visible) {
J
Joao Moreno 已提交
227
			node.renderNodeCount = 0;
J
Joao Moreno 已提交
228 229 230 231

			if (revealed) {
				treeListElements.pop();
			}
J
Joao Moreno 已提交
232
		} else if (!node.collapsed) {
J
Joao Moreno 已提交
233
			node.renderNodeCount = renderNodeCount;
234 235
		}

J
Joao Moreno 已提交
236 237 238 239
		if (onDidCreateNode) {
			onDidCreateNode(node);
		}

240 241 242
		return node;
	}

J
Joao Moreno 已提交
243
	private updateNodeAfterCollapseChange(node: IMutableTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
J
Joao Moreno 已提交
244
		const previousRenderNodeCount = node.renderNodeCount;
245 246
		const result: ITreeNode<T, TFilterData>[] = [];

J
Joao Moreno 已提交
247
		this._updateNodeAfterCollapseChange(node, result);
J
Joao Moreno 已提交
248
		this._updateAncestorsRenderNodeCount(node.parent, result.length - previousRenderNodeCount);
J
Joao Moreno 已提交
249 250 251 252 253 254 255 256 257 258

		return result;
	}

	private _updateNodeAfterCollapseChange(node: IMutableTreeNode<T, TFilterData>, result: ITreeNode<T, TFilterData>[]): number {
		if (node.visible === false) {
			return 0;
		}

		result.push(node);
J
Joao Moreno 已提交
259
		node.renderNodeCount = 1;
J
Joao Moreno 已提交
260 261 262

		if (!node.collapsed) {
			for (const child of node.children) {
J
Joao Moreno 已提交
263
				node.renderNodeCount += this._updateNodeAfterCollapseChange(child, result);
264
			}
J
Joao Moreno 已提交
265 266
		}

J
Joao Moreno 已提交
267
		this._onDidChangeRenderNodeCount.fire(node);
J
Joao Moreno 已提交
268
		return node.renderNodeCount;
J
Joao Moreno 已提交
269 270 271
	}

	private updateNodeAfterFilterChange(node: IMutableTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
J
Joao Moreno 已提交
272
		const previousRenderNodeCount = node.renderNodeCount;
J
Joao Moreno 已提交
273 274 275
		const result: ITreeNode<T, TFilterData>[] = [];

		this._updateNodeAfterFilterChange(node, result);
J
Joao Moreno 已提交
276
		this._updateAncestorsRenderNodeCount(node.parent, result.length - previousRenderNodeCount);
J
Joao Moreno 已提交
277 278 279 280 281

		return result;
	}

	private _updateNodeAfterFilterChange(node: IMutableTreeNode<T, TFilterData>, result: ITreeNode<T, TFilterData>[], revealed = true): boolean {
J
Joao Moreno 已提交
282 283
		let visible: boolean | undefined;

J
Joao Moreno 已提交
284
		if (node !== this.root) {
J
Joao Moreno 已提交
285
			visible = this._filterNode(node);
286

J
Joao Moreno 已提交
287 288
			if (visible === false) {
				node.visible = false;
J
Joao Moreno 已提交
289
				return false;
290 291
			}

J
Joao Moreno 已提交
292 293 294
			if (revealed) {
				result.push(node);
			}
J
Joao Moreno 已提交
295
		}
J
Joao Moreno 已提交
296

J
Joao Moreno 已提交
297
		const resultStartLength = result.length;
J
Joao Moreno 已提交
298
		node.renderNodeCount = node === this.root ? 0 : 1;
299

J
Joao Moreno 已提交
300
		let hasVisibleDescendants = false;
J
Joao Moreno 已提交
301
		if (visible !== false || !node.collapsed) {
J
Joao Moreno 已提交
302 303
			for (const child of node.children) {
				hasVisibleDescendants = this._updateNodeAfterFilterChange(child, result, revealed && !node.collapsed) || hasVisibleDescendants;
J
Joao Moreno 已提交
304
			}
J
Joao Moreno 已提交
305
		}
J
Joao Moreno 已提交
306

J
Joao Moreno 已提交
307 308
		if (node !== this.root) {
			node.visible = typeof visible === 'undefined' ? hasVisibleDescendants : visible;
J
Joao Moreno 已提交
309
		}
J
Joao Moreno 已提交
310

J
Joao Moreno 已提交
311
		if (!node.visible) {
J
Joao Moreno 已提交
312
			node.renderNodeCount = 0;
313

J
Joao Moreno 已提交
314 315 316 317
			if (revealed) {
				result.pop();
			}
		} else if (!node.collapsed) {
J
Joao Moreno 已提交
318
			node.renderNodeCount += result.length - resultStartLength;
J
Joao Moreno 已提交
319
		}
320

J
Joao Moreno 已提交
321
		this._onDidChangeRenderNodeCount.fire(node);
J
Joao Moreno 已提交
322 323
		return node.visible;
	}
J
Joao Moreno 已提交
324

J
Joao Moreno 已提交
325
	private _updateAncestorsRenderNodeCount(node: IMutableTreeNode<T, TFilterData>, diff: number): void {
J
Joao Moreno 已提交
326 327
		if (diff === 0) {
			return;
J
Joao Moreno 已提交
328 329
		}

330
		while (node) {
J
Joao Moreno 已提交
331
			node.renderNodeCount += diff;
J
Joao Moreno 已提交
332
			this._onDidChangeRenderNodeCount.fire(node);
333
			node = node.parent;
J
Joao Moreno 已提交
334 335 336
		}
	}

J
Joao Moreno 已提交
337
	private _filterNode(node: IMutableTreeNode<T, TFilterData>): boolean | undefined {
J
Joao Moreno 已提交
338
		const result = this.filter ? this.filter.filter(node.element) : TreeVisibility.Visible;
339

J
Joao Moreno 已提交
340
		if (typeof result === 'boolean') {
J
Joao Moreno 已提交
341
			node.filterData = undefined;
J
Joao Moreno 已提交
342
			return result;
J
Joao Moreno 已提交
343 344
		} else if (isFilterResult<TFilterData>(result)) {
			node.filterData = result.data;
J
Joao Moreno 已提交
345
			return getVisibleState(result.visibility);
346 347
		} else {
			node.filterData = undefined;
J
Joao Moreno 已提交
348
			return getVisibleState(result);
349
		}
J
Joao Moreno 已提交
350 351
	}

J
Joao Moreno 已提交
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
	/**
	 * Cheaper version of findNode, which doesn't require list indices.
	 */
	private getNode(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root): IMutableTreeNode<T, TFilterData> {
		if (location.length === 0) {
			return node;
		}

		const [index, ...rest] = location;

		if (index < 0 || index > node.children.length) {
			throw new Error('Invalid tree location');
		}

		return this.getNode(rest, node.children[index]);
	}

	private getNodeWithListIndex(location: number[]): { node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean } {
		const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location);
J
Joao Moreno 已提交
371
		const index = location[location.length - 1];
J
Joao Moreno 已提交
372 373 374 375 376 377 378

		if (index < 0 || index > parentNode.children.length) {
			throw new Error('Invalid tree location');
		}

		const node = parentNode.children[index];

379
		return { node, listIndex, revealed };
J
Joao Moreno 已提交
380 381
	}

J
Joao Moreno 已提交
382
	private getParentNodeWithListIndex(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true): { parentNode: IMutableTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; } {
J
Joao Moreno 已提交
383 384 385 386 387
		const [index, ...rest] = location;

		if (index < 0 || index > node.children.length) {
			throw new Error('Invalid tree location');
		}
J
Joao Moreno 已提交
388

389
		// TODO@joao perf!
J
Joao Moreno 已提交
390
		for (let i = 0; i < index; i++) {
J
Joao Moreno 已提交
391
			listIndex += node.children[i].renderNodeCount;
392
		}
J
Joao Moreno 已提交
393

394
		revealed = revealed && !node.collapsed;
J
Joao Moreno 已提交
395 396

		if (rest.length === 0) {
397
			return { parentNode: node, listIndex, revealed };
J
Joao Moreno 已提交
398 399
		}

J
Joao Moreno 已提交
400
		return this.getParentNodeWithListIndex(rest, node.children[index], listIndex + 1, revealed);
J
Joao Moreno 已提交
401
	}
J
Joao Moreno 已提交
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421

	// TODO@joao perf!
	getNodeLocation(node: ITreeNode<T, TFilterData>): number[] {
		const location = [];

		while (node.parent) {
			location.push(node.parent.children.indexOf(node));
			node = node.parent;
		}

		return location.reverse();
	}

	getParentNodeLocation(location: number[]): number[] | null {
		if (location.length <= 1) {
			return null;
		}

		return tail2(location)[0];
	}
J
Joao Moreno 已提交
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455

	getParentElement(location: number[]): T | null {
		const parentLocation = this.getParentNodeLocation(location);
		const node = this.getNode(parentLocation);
		return node === this.root ? null : node.element;
	}

	getFirstElementChild(location: number[]): T | null {
		const node = this.getNode(location);

		if (node.children.length === 0) {
			return null;
		}

		return node.children[0].element;
	}

	getLastElementAncestor(location: number[]): T | null {
		const node = this.getNode(location);

		if (node.children.length === 0) {
			return null;
		}

		return this._getLastElementAncestor(node);
	}

	private _getLastElementAncestor(node: ITreeNode<T, TFilterData>): T | null {
		if (node.children.length === 0) {
			return node.element;
		}

		return this._getLastElementAncestor(node.children[node.children.length - 1]);
	}
J
Joao Moreno 已提交
456
}