indexTreeModel.ts 14.1 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, ITreeModel, ITreeNode, ITreeElement, ITreeModelOptions } 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
}

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

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

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

J
Joao Moreno 已提交
55 56
	private eventBufferer = new EventBufferer();

J
Joao Moreno 已提交
57
	private _onDidChangeCollapseState = new Emitter<ITreeNode<T, TFilterData>>();
J
Joao Moreno 已提交
58 59 60 61
	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);
62

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

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

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

J
Joao Moreno 已提交
80
		const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location);
J
Joao Moreno 已提交
81
		const treeListElementsToInsert: ITreeNode<T, TFilterData>[] = [];
82
		const nodesToInsertIterator = Iterator.map(Iterator.from(toInsert), el => this.createTreeNode(el, parentNode, parentNode.visible ? TreeVisibility.Visible : TreeVisibility.Hidden, revealed, treeListElementsToInsert, onDidCreateNode));
J
Joao Moreno 已提交
83 84

		const nodesToInsert: IMutableTreeNode<T, TFilterData>[] = [];
J
Joao Moreno 已提交
85
		let renderNodeCount = 0;
J
Joao Moreno 已提交
86 87 88

		Iterator.forEach(nodesToInsertIterator, node => {
			nodesToInsert.push(node);
J
Joao Moreno 已提交
89
			renderNodeCount += node.renderNodeCount;
J
Joao Moreno 已提交
90 91
		});

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

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

J
Joao Moreno 已提交
98
			this._updateAncestorsRenderNodeCount(parentNode, renderNodeCount - visibleDeleteCount);
99
			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
	getListIndex(location: number[]): number {
J
Joao Moreno 已提交
115
		return this.getTreeNodeWithListIndex(location).listIndex;
J
Joao Moreno 已提交
116 117
	}

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

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

J
Joao Moreno 已提交
128
	collapseAll(): void {
J
Joao Moreno 已提交
129
		const queue = [...this.root.children];
J
Joao Moreno 已提交
130 131 132 133
		let listIndex = 0;

		this.eventBufferer.bufferEvents(() => {
			while (queue.length > 0) {
134
				const node = queue.shift()!;
J
Joao Moreno 已提交
135 136 137 138 139 140 141 142
				const revealed = listIndex < this.root.children.length;
				this._setCollapsed(node, listIndex, revealed, true);

				queue.push(...node.children);
				listIndex++;
			}
		});
	}
143 144

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

J
Joao Moreno 已提交
148
	refilter(): void {
J
Joao Moreno 已提交
149
		const previousRenderNodeCount = this.root.renderNodeCount;
J
Joao Moreno 已提交
150
		const toInsert = this.updateNodeAfterFilterChange(this.root);
J
Joao Moreno 已提交
151
		this.list.splice(0, previousRenderNodeCount, 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 previousRenderNodeCount = node.renderNodeCount;
J
Joao Moreno 已提交
171
			const toInsert = this.updateNodeAfterCollapseChange(node);
172

J
Joao Moreno 已提交
173
			this.list.splice(listIndex + 1, previousRenderNodeCount - 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
	private createTreeNode(
		treeElement: ITreeElement<T>,
		parent: IMutableTreeNode<T, TFilterData>,
183
		parentVisibility: TreeVisibility,
J
Joao Moreno 已提交
184 185 186 187
		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
			collapsed: !!treeElement.collapsed,
J
Joao Moreno 已提交
195
			renderNodeCount: 1,
J
Joao Moreno 已提交
196 197 198
			visible: true,
			filterData: undefined
		};
J
Joao Moreno 已提交
199

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

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

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

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

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

219
		node.collapsible = node.collapsible || node.children.length > 0;
220
		node.visible = visibility === TreeVisibility.Recurse ? hasVisibleDescendants : (visibility === TreeVisibility.Visible);
J
Joao Moreno 已提交
221

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

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

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

236 237 238
		return node;
	}

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

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

		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 已提交
255
		node.renderNodeCount = 1;
J
Joao Moreno 已提交
256 257 258

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

J
Joao Moreno 已提交
263
		this._onDidChangeRenderNodeCount.fire(node);
J
Joao Moreno 已提交
264
		return node.renderNodeCount;
J
Joao Moreno 已提交
265 266 267
	}

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

271
		this._updateNodeAfterFilterChange(node, node.visible ? TreeVisibility.Visible : TreeVisibility.Hidden, result);
J
Joao Moreno 已提交
272
		this._updateAncestorsRenderNodeCount(node.parent, result.length - previousRenderNodeCount);
J
Joao Moreno 已提交
273 274 275 276

		return result;
	}

277 278
	private _updateNodeAfterFilterChange(node: IMutableTreeNode<T, TFilterData>, parentVisibility: TreeVisibility, result: ITreeNode<T, TFilterData>[], revealed = true): boolean {
		let visibility: TreeVisibility;
J
Joao Moreno 已提交
279

J
Joao Moreno 已提交
280
		if (node !== this.root) {
281
			visibility = this._filterNode(node, parentVisibility);
282

283
			if (visibility === TreeVisibility.Hidden) {
J
Joao Moreno 已提交
284
				node.visible = false;
J
Joao Moreno 已提交
285
				return false;
286 287
			}

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

J
Joao Moreno 已提交
293
		const resultStartLength = result.length;
J
Joao Moreno 已提交
294
		node.renderNodeCount = node === this.root ? 0 : 1;
295

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

J
Joao Moreno 已提交
303
		if (node !== this.root) {
304
			node.visible = visibility! === TreeVisibility.Recurse ? hasVisibleDescendants : (visibility! === TreeVisibility.Visible);
J
Joao Moreno 已提交
305
		}
J
Joao Moreno 已提交
306

J
Joao Moreno 已提交
307
		if (!node.visible) {
J
Joao Moreno 已提交
308
			node.renderNodeCount = 0;
309

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

J
Joao Moreno 已提交
317
		this._onDidChangeRenderNodeCount.fire(node);
J
Joao Moreno 已提交
318 319
		return node.visible;
	}
J
Joao Moreno 已提交
320

321
	private _updateAncestorsRenderNodeCount(node: IMutableTreeNode<T, TFilterData> | undefined, diff: number): void {
J
Joao Moreno 已提交
322 323
		if (diff === 0) {
			return;
J
Joao Moreno 已提交
324 325
		}

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

333 334
	private _filterNode(node: IMutableTreeNode<T, TFilterData>, parentVisibility: TreeVisibility): TreeVisibility {
		const result = this.filter ? this.filter.filter(node.element, parentVisibility) : TreeVisibility.Visible;
335

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

J
Joao Moreno 已提交
348
	// cheap
349 350
	private getTreeNode(location: number[] | null, node: IMutableTreeNode<T, TFilterData> = this.root): IMutableTreeNode<T, TFilterData> {
		if (!location || location.length === 0) {
J
Joao Moreno 已提交
351 352 353 354 355 356 357 358 359
			return node;
		}

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

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

J
Joao Moreno 已提交
360
		return this.getTreeNode(rest, node.children[index]);
J
Joao Moreno 已提交
361 362
	}

J
Joao Moreno 已提交
363 364
	// expensive
	private getTreeNodeWithListIndex(location: number[]): { node: IMutableTreeNode<T, TFilterData>, listIndex: number, revealed: boolean } {
J
Joao Moreno 已提交
365
		const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location);
J
Joao Moreno 已提交
366
		const index = location[location.length - 1];
J
Joao Moreno 已提交
367 368 369 370 371 372 373

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

		const node = parentNode.children[index];

374
		return { node, listIndex, revealed };
J
Joao Moreno 已提交
375 376
	}

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

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

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

389
		revealed = revealed && !node.collapsed;
J
Joao Moreno 已提交
390 391

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

J
Joao Moreno 已提交
395
		return this.getParentNodeWithListIndex(rest, node.children[index], listIndex + 1, revealed);
J
Joao Moreno 已提交
396
	}
J
Joao Moreno 已提交
397

J
Joao Moreno 已提交
398 399 400 401
	getNode(location: number[] = []): ITreeNode<T, TFilterData> {
		return this.getTreeNode(location);
	}

J
Joao Moreno 已提交
402 403
	// TODO@joao perf!
	getNodeLocation(node: ITreeNode<T, TFilterData>): number[] {
M
Matt Bierner 已提交
404
		const location: number[] = [];
J
Joao Moreno 已提交
405 406 407 408 409 410 411 412 413 414 415

		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) {
J
Joao Moreno 已提交
416
			return [];
J
Joao Moreno 已提交
417 418 419 420
		}

		return tail2(location)[0];
	}
J
Joao Moreno 已提交
421 422 423

	getParentElement(location: number[]): T | null {
		const parentLocation = this.getParentNodeLocation(location);
J
Joao Moreno 已提交
424
		const node = this.getTreeNode(parentLocation);
J
Joao Moreno 已提交
425 426 427
		return node === this.root ? null : node.element;
	}

J
Joao Moreno 已提交
428
	getFirstChildElement(location: number[]): T | null {
J
Joao Moreno 已提交
429
		const node = this.getTreeNode(location);
J
Joao Moreno 已提交
430 431 432 433 434 435 436 437

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

		return node.children[0].element;
	}

J
Joao Moreno 已提交
438
	getLastAncestorElement(location: number[]): T | null {
J
Joao Moreno 已提交
439
		const node = this.getTreeNode(location);
J
Joao Moreno 已提交
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454

		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
}