indexTreeModel.ts 13.2 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';
8
import { Emitter, Event } 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;
17
	revealedCount: 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
		revealedCount: 0,
J
Joao Moreno 已提交
55 56
		visible: true,
		filterData: undefined
J
Joao Moreno 已提交
57
	};
58

J
Joao Moreno 已提交
59 60
	private _onDidChangeCollapseState = new Emitter<ITreeNode<T, TFilterData>>();
	readonly onDidChangeCollapseState: Event<ITreeNode<T, TFilterData>> = this._onDidChangeCollapseState.event;
61

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

J
Joao Moreno 已提交
64
	constructor(private list: ISpliceable<ITreeNode<T, TFilterData>>, options: IIndexTreeModelOptions<T, TFilterData> = {}) {
65 66
		this.filter = options.filter;
	}
J
Joao Moreno 已提交
67

J
Joao Moreno 已提交
68 69 70 71 72 73 74
	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 已提交
75
		if (location.length === 0) {
J
Joao Moreno 已提交
76 77 78
			throw new Error('Invalid tree location');
		}

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

		const nodesToInsert: IMutableTreeNode<T, TFilterData>[] = [];
		let revealedCount = 0;

		Iterator.forEach(nodesToInsertIterator, node => {
			nodesToInsert.push(node);
			revealedCount += node.revealedCount;
		});

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

94
		if (revealed) {
95
			const visibleDeleteCount = deletedNodes.reduce((r, node) => r + node.revealedCount, 0);
96

97
			this._updateAncestorsRevealedCount(parentNode, revealedCount - visibleDeleteCount);
98
			this.list.splice(listIndex, visibleDeleteCount, treeListElementsToInsert);
J
Joao Moreno 已提交
99
		}
J
Joao Moreno 已提交
100

101
		if (deletedNodes.length > 0 && onDidDeleteNode) {
J
Joao Moreno 已提交
102 103 104 105 106 107 108 109
			const visit = (node: ITreeNode<T, TFilterData>) => {
				onDidDeleteNode(node);
				node.children.forEach(visit);
			};

			deletedNodes.forEach(visit);
		}

J
Joao Moreno 已提交
110
		return Iterator.map(Iterator.fromArray(deletedNodes), treeNodeToElement);
J
Joao Moreno 已提交
111 112
	}

J
Joao Moreno 已提交
113
	getListIndex(location: number[]): number {
J
Joao Moreno 已提交
114
		return this.getNodeWithListIndex(location).listIndex;
J
Joao Moreno 已提交
115 116
	}

J
Joao Moreno 已提交
117
	setCollapsed(location: number[], collapsed: boolean): boolean {
J
Joao Moreno 已提交
118
		const { node, listIndex, revealed } = this.getNodeWithListIndex(location);
119
		return this._setCollapsed(node, listIndex, revealed, collapsed);
J
Joao Moreno 已提交
120 121 122
	}

	toggleCollapsed(location: number[]): void {
J
Joao Moreno 已提交
123
		const { node, listIndex, revealed } = this.getNodeWithListIndex(location);
124
		this._setCollapsed(node, listIndex, revealed);
J
Joao Moreno 已提交
125
	}
J
Joao Moreno 已提交
126

J
Joao Moreno 已提交
127 128 129 130 131
	// // TODO@joao cleanup
	// setCollapsedAll(collapsed: boolean): void {
	// 	if (collapsed) {
	// 		const queue = [...this.root.children]; // TODO@joao use a linked list
	// 		let listIndex = 0;
132

J
Joao Moreno 已提交
133 134 135 136
	// 		while (queue.length > 0) {
	// 			const node = queue.shift();
	// 			const revealed = listIndex < this.root.children.length;
	// 			this._setCollapsed(node, listIndex, revealed, collapsed);
137

J
Joao Moreno 已提交
138 139 140 141 142
	// 			queue.push(...node.children);
	// 			listIndex++;
	// 		}
	// 	}
	// }
143 144

	isCollapsed(location: number[]): boolean {
J
Joao Moreno 已提交
145
		return this.getNode(location).collapsed;
146 147
	}

J
Joao Moreno 已提交
148
	refilter(): void {
J
Joao Moreno 已提交
149 150 151
		const previousRevealedCount = this.root.revealedCount;
		const toInsert = this.updateNodeAfterFilterChange(this.root);
		this.list.splice(0, previousRevealedCount, toInsert);
J
Joao Moreno 已提交
152 153
	}

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

J
Joao Moreno 已提交
159 160 161 162
		if (typeof collapsed === 'undefined') {
			collapsed = !node.collapsed;
		}

J
Joao Moreno 已提交
163
		if (node.collapsed === collapsed) {
J
Joao Moreno 已提交
164
			return false;
J
Joao Moreno 已提交
165 166 167 168
		}

		node.collapsed = collapsed;

169
		if (revealed) {
J
Joao Moreno 已提交
170
			const previousRevealedCount = node.revealedCount;
J
Joao Moreno 已提交
171
			const toInsert = this.updateNodeAfterCollapseChange(node);
172

J
Joao Moreno 已提交
173
			this.list.splice(listIndex + 1, previousRevealedCount - 1, toInsert.slice(1));
J
Joao Moreno 已提交
174
			this._onDidChangeCollapseState.fire(node);
J
Joao Moreno 已提交
175
		}
J
Joao Moreno 已提交
176 177

		return true;
J
Joao Moreno 已提交
178 179
	}

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

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

J
Joao Moreno 已提交
201
		if (revealed) {
202 203 204
			treeListElements.push(node);
		}

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

209
		let hasVisibleDescendants = false;
J
Joao Moreno 已提交
210
		let revealedCount = 1;
211 212 213 214

		Iterator.forEach(childNodes, child => {
			node.children.push(child);
			hasVisibleDescendants = hasVisibleDescendants || child.visible;
J
Joao Moreno 已提交
215
			revealedCount += child.revealedCount;
216 217
		});

218
		node.collapsible = node.collapsible || node.children.length > 0;
J
Joao Moreno 已提交
219
		node.visible = typeof visible === 'undefined' ? hasVisibleDescendants : visible;
J
Joao Moreno 已提交
220

J
Joao Moreno 已提交
221 222 223 224 225 226
		if (!node.visible) {
			node.revealedCount = 0;

			if (revealed) {
				treeListElements.pop();
			}
J
Joao Moreno 已提交
227
		} else if (!node.collapsed) {
J
Joao Moreno 已提交
228
			node.revealedCount = revealedCount;
229 230
		}

J
Joao Moreno 已提交
231 232 233 234
		if (onDidCreateNode) {
			onDidCreateNode(node);
		}

235 236 237
		return node;
	}

J
Joao Moreno 已提交
238
	private updateNodeAfterCollapseChange(node: IMutableTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
J
Joao Moreno 已提交
239
		const previousRevealedCount = node.revealedCount;
240 241
		const result: ITreeNode<T, TFilterData>[] = [];

J
Joao Moreno 已提交
242
		this._updateNodeAfterCollapseChange(node, result);
J
Joao Moreno 已提交
243
		this._updateAncestorsRevealedCount(node.parent, result.length - previousRevealedCount);
J
Joao Moreno 已提交
244 245 246 247 248 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);
		node.revealedCount = 1;

		if (!node.collapsed) {
			for (const child of node.children) {
				node.revealedCount += this._updateNodeAfterCollapseChange(child, result);
259
			}
J
Joao Moreno 已提交
260 261 262 263 264 265 266 267 268 269
		}

		return node.revealedCount;
	}

	private updateNodeAfterFilterChange(node: IMutableTreeNode<T, TFilterData>): ITreeNode<T, TFilterData>[] {
		const previousRevealedCount = node.revealedCount;
		const result: ITreeNode<T, TFilterData>[] = [];

		this._updateNodeAfterFilterChange(node, result);
J
Joao Moreno 已提交
270
		this._updateAncestorsRevealedCount(node.parent, result.length - previousRevealedCount);
J
Joao Moreno 已提交
271 272 273 274 275

		return result;
	}

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

J
Joao Moreno 已提交
278
		if (node !== this.root) {
J
Joao Moreno 已提交
279
			visible = this._filterNode(node);
280

J
Joao Moreno 已提交
281 282
			if (visible === false) {
				node.visible = false;
J
Joao Moreno 已提交
283
				return false;
284 285
			}

J
Joao Moreno 已提交
286 287 288
			if (revealed) {
				result.push(node);
			}
J
Joao Moreno 已提交
289
		}
J
Joao Moreno 已提交
290

J
Joao Moreno 已提交
291 292
		const resultStartLength = result.length;
		node.revealedCount = node === this.root ? 0 : 1;
293

J
Joao Moreno 已提交
294
		let hasVisibleDescendants = false;
J
Joao Moreno 已提交
295
		if (visible !== false || !node.collapsed) {
J
Joao Moreno 已提交
296 297
			for (const child of node.children) {
				hasVisibleDescendants = this._updateNodeAfterFilterChange(child, result, revealed && !node.collapsed) || hasVisibleDescendants;
J
Joao Moreno 已提交
298
			}
J
Joao Moreno 已提交
299
		}
J
Joao Moreno 已提交
300

J
Joao Moreno 已提交
301 302
		if (node !== this.root) {
			node.visible = typeof visible === 'undefined' ? hasVisibleDescendants : visible;
J
Joao Moreno 已提交
303
		}
J
Joao Moreno 已提交
304

J
Joao Moreno 已提交
305 306
		if (!node.visible) {
			node.revealedCount = 0;
307

J
Joao Moreno 已提交
308 309 310 311 312 313
			if (revealed) {
				result.pop();
			}
		} else if (!node.collapsed) {
			node.revealedCount += result.length - resultStartLength;
		}
314

J
Joao Moreno 已提交
315 316
		return node.visible;
	}
J
Joao Moreno 已提交
317

J
Joao Moreno 已提交
318
	private _updateAncestorsRevealedCount(node: IMutableTreeNode<T, TFilterData>, diff: number): void {
J
Joao Moreno 已提交
319 320
		if (diff === 0) {
			return;
J
Joao Moreno 已提交
321 322
		}

323
		while (node) {
J
Joao Moreno 已提交
324
			node.revealedCount += diff;
325
			node = node.parent;
J
Joao Moreno 已提交
326 327 328
		}
	}

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

J
Joao Moreno 已提交
332
		if (typeof result === 'boolean') {
J
Joao Moreno 已提交
333
			node.filterData = undefined;
J
Joao Moreno 已提交
334
			return result;
J
Joao Moreno 已提交
335 336
		} else if (isFilterResult<TFilterData>(result)) {
			node.filterData = result.data;
J
Joao Moreno 已提交
337
			return getVisibleState(result.visibility);
338 339
		} else {
			node.filterData = undefined;
J
Joao Moreno 已提交
340
			return getVisibleState(result);
341
		}
J
Joao Moreno 已提交
342 343
	}

J
Joao Moreno 已提交
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
	/**
	 * 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 已提交
363
		const index = location[location.length - 1];
J
Joao Moreno 已提交
364 365 366 367 368 369 370

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

		const node = parentNode.children[index];

371
		return { node, listIndex, revealed };
J
Joao Moreno 已提交
372 373
	}

J
Joao Moreno 已提交
374
	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 已提交
375 376 377 378 379
		const [index, ...rest] = location;

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

381
		// TODO@joao perf!
J
Joao Moreno 已提交
382
		for (let i = 0; i < index; i++) {
383
			listIndex += node.children[i].revealedCount;
384
		}
J
Joao Moreno 已提交
385

386
		revealed = revealed && !node.collapsed;
J
Joao Moreno 已提交
387 388

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

J
Joao Moreno 已提交
392
		return this.getParentNodeWithListIndex(rest, node.children[index], listIndex + 1, revealed);
J
Joao Moreno 已提交
393
	}
J
Joao Moreno 已提交
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413

	// 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 已提交
414 415 416 417 418 419 420 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

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