indexTreeModel.ts 11.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';
8
import { Emitter, Event } from 'vs/base/common/event';
J
Joao Moreno 已提交
9 10
import { tail2 } from 'vs/base/common/arrays';
import { ITreeFilterResult, 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 ITreeFilterResult<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: TreeVisibility): boolean | undefined {
J
Joao Moreno 已提交
34
	switch (visibility) {
J
Joao Moreno 已提交
35 36 37
		case TreeVisibility.Hidden: return false;
		case TreeVisibility.Visible: return true;
		case TreeVisibility.Recurse: return undefined;
J
Joao Moreno 已提交
38 39 40
	}
}

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

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

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

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

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

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

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

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

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

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

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

92 93
		if (!parentNode.collapsed) {
			const visibleDeleteCount = deletedNodes.reduce((r, node) => r + node.revealedCount, 0);
94

95 96 97 98 99
			this._updateAncestorsRevealedCount(parentNode, revealedCount - visibleDeleteCount);

			if (revealed) {
				this.list.splice(listIndex, visibleDeleteCount, treeListElementsToInsert);
			}
J
Joao Moreno 已提交
100
		}
J
Joao Moreno 已提交
101

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

			deletedNodes.forEach(visit);
		}

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

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

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

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

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

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

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

	isCollapsed(location: number[]): boolean {
		return this.findNode(location).node.collapsed;
	}

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

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

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

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

		node.collapsed = collapsed;

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

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

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

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

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

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

J
Joao Moreno 已提交
206 207
		const childElements = Iterator.from(treeElement.children);
		const childNodes = Iterator.map(childElements, el => this.createTreeNode(el, node, revealed && !treeElement.collapsed, 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
	}

344 345
	private findNode(location: number[]): { node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean } {
		const { parentNode, listIndex, revealed } = this.findParentNode(location);
J
Joao Moreno 已提交
346
		const index = location[location.length - 1];
J
Joao Moreno 已提交
347 348 349 350 351 352 353

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

		const node = parentNode.children[index];

354
		return { node, listIndex, revealed };
J
Joao Moreno 已提交
355 356
	}

357
	private findParentNode(location: number[], node: IMutableTreeNode<T, TFilterData> = this.root, listIndex: number = 0, revealed = true): { parentNode: IMutableTreeNode<T, TFilterData>; listIndex: number; revealed: boolean; } {
J
Joao Moreno 已提交
358 359 360 361 362
		const [index, ...rest] = location;

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

364
		// TODO@joao perf!
J
Joao Moreno 已提交
365
		for (let i = 0; i < index; i++) {
366
			listIndex += node.children[i].revealedCount;
367
		}
J
Joao Moreno 已提交
368

369
		revealed = revealed && !node.collapsed;
J
Joao Moreno 已提交
370 371

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

375
		return this.findParentNode(rest, node.children[index], listIndex + 1, revealed);
J
Joao Moreno 已提交
376
	}
J
Joao Moreno 已提交
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396

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