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 137 138 139 140 141 142 143 144 145 146
	collapseAll(): void {
		const queue = [...this.root.children]; // TODO@joao use a linked list
		let listIndex = 0;

		this.eventBufferer.bufferEvents(() => {
			while (queue.length > 0) {
				const node = queue.shift();
				const revealed = listIndex < this.root.children.length;
				this._setCollapsed(node, listIndex, revealed, true);

				queue.push(...node.children);
				listIndex++;
			}
		});
	}
147 148

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

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

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

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

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

		node.collapsed = collapsed;

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

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

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

J
Joao Moreno 已提交
184 185 186 187 188 189 190
	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 已提交
191 192 193 194 195
		const node: IMutableTreeNode<T, TFilterData> = {
			parent,
			element: treeElement.element,
			children: [],
			depth: parent.depth + 1,
196
			collapsible: typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : (typeof treeElement.collapsed === 'boolean'),
J
Joao Moreno 已提交
197
			collapsed: !!treeElement.collapsed,
J
Joao Moreno 已提交
198
			renderNodeCount: 1,
J
Joao Moreno 已提交
199 200 201
			visible: true,
			filterData: undefined
		};
J
Joao Moreno 已提交
202

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

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

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

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

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

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

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

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

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

239 240 241
		return node;
	}

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

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

		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 已提交
258
		node.renderNodeCount = 1;
J
Joao Moreno 已提交
259 260 261

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

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

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

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

		return result;
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

J
Joao Moreno 已提交
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
	/**
	 * 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 已提交
370
		const index = location[location.length - 1];
J
Joao Moreno 已提交
371 372 373 374 375 376 377

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

		const node = parentNode.children[index];

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

J
Joao Moreno 已提交
381
	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 已提交
382 383 384 385 386
		const [index, ...rest] = location;

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

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

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

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

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

	// 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 已提交
421 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

	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 已提交
455
}