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
	filterData: TFilterData | undefined;
J
Joao Moreno 已提交
19
	visible: boolean;
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 206
		const childElements = Iterator.from(treeElement.children);
		const childNodes = Iterator.map(childElements, el => this.createTreeNode(el, node, revealed && !treeElement.collapsed, treeListElements, onDidCreateNode));
J
Joao Moreno 已提交
207

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

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

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

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

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

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

234 235 236
		return node;
	}

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

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

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

		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 已提交
269
		this._updateAncestorsRevealedCount(node.parent, result.length - previousRevealedCount);
J
Joao Moreno 已提交
270 271 272 273 274

		return result;
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		const node = parentNode.children[index];

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

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

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

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

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

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

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

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

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