dataTree.ts 6.4 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
import { ISpliceable } from 'vs/base/common/sequence';
J
Joao Moreno 已提交
8
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, IDataSource, TreeError } from 'vs/base/browser/ui/tree/tree';
J
Joao Moreno 已提交
9
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
J
Joao Moreno 已提交
10
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
J
Joao Moreno 已提交
11 12 13
import { Iterator } from 'vs/base/common/iterator';

export interface IDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
J
Joao Moreno 已提交
14
	readonly sorter?: ITreeSorter<T>;
J
Joao Moreno 已提交
15 16
}

J
Joao Moreno 已提交
17 18 19
export interface IDataTreeViewState {
	readonly focus: string[];
	readonly selection: string[];
J
Joao Moreno 已提交
20
	readonly expanded: string[];
21
	readonly scrollTop: number;
J
Joao Moreno 已提交
22 23
}

J
Joao Moreno 已提交
24
export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | null, TFilterData, T | null> {
J
Joao Moreno 已提交
25

J
Joao Moreno 已提交
26
	protected model!: ObjectTreeModel<T, TFilterData>;
J
Joao Moreno 已提交
27
	private input: TInput | undefined;
J
Joao Moreno 已提交
28

J
Joao Moreno 已提交
29
	private identityProvider: IIdentityProvider<T> | undefined;
J
Joao Moreno 已提交
30
	private nodesByIdentity = new Map<string, ITreeNode<T, TFilterData>>();
J
Joao Moreno 已提交
31

J
Joao Moreno 已提交
32
	constructor(
J
Joao Moreno 已提交
33
		private user: string,
J
Joao Moreno 已提交
34 35
		container: HTMLElement,
		delegate: IListVirtualDelegate<T>,
36
		renderers: ITreeRenderer<T, TFilterData, any>[],
J
Joao Moreno 已提交
37 38 39
		private dataSource: IDataSource<TInput, T>,
		options: IDataTreeOptions<T, TFilterData> = {}
	) {
J
Joao Moreno 已提交
40
		super(user, container, delegate, renderers, options as IDataTreeOptions<T | null, TFilterData>);
J
Joao Moreno 已提交
41
		this.identityProvider = options.identityProvider;
J
Joao Moreno 已提交
42 43
	}

44 45
	// Model

J
Joao Moreno 已提交
46 47 48 49
	getInput(): TInput | undefined {
		return this.input;
	}

J
Joao Moreno 已提交
50 51
	setInput(input: TInput, viewState?: IDataTreeViewState): void {
		if (viewState && !this.identityProvider) {
J
Joao Moreno 已提交
52
			throw new TreeError(this.user, 'Can\'t restore tree view state without an identity provider');
J
Joao Moreno 已提交
53 54
		}

J
Joao Moreno 已提交
55
		this.input = input;
J
Joao Moreno 已提交
56 57 58 59 60 61 62 63 64 65 66

		if (!viewState) {
			this._refresh(input);
			return;
		}

		const focus: T[] = [];
		const selection: T[] = [];

		const isCollapsed = (element: T) => {
			const id = this.identityProvider!.getId(element).toString();
J
Joao Moreno 已提交
67 68 69 70 71
			return viewState.expanded.indexOf(id) === -1;
		};

		const onDidCreateNode = (node: ITreeNode<T, TFilterData>) => {
			const id = this.identityProvider!.getId(node.element).toString();
J
Joao Moreno 已提交
72 73

			if (viewState.focus.indexOf(id) > -1) {
J
Joao Moreno 已提交
74
				focus.push(node.element);
J
Joao Moreno 已提交
75 76 77
			}

			if (viewState.selection.indexOf(id) > -1) {
J
Joao Moreno 已提交
78
				selection.push(node.element);
J
Joao Moreno 已提交
79 80 81
			}
		};

J
Joao Moreno 已提交
82
		this._refresh(input, isCollapsed, onDidCreateNode);
J
Joao Moreno 已提交
83 84
		this.setFocus(focus);
		this.setSelection(selection);
85 86 87 88

		if (viewState && typeof viewState.scrollTop === 'number') {
			this.scrollTop = viewState.scrollTop;
		}
J
Joao Moreno 已提交
89 90
	}

91
	updateChildren(element: TInput | T = this.input!): void {
J
Joao Moreno 已提交
92
		if (typeof this.input === 'undefined') {
J
Joao Moreno 已提交
93
			throw new TreeError(this.user, 'Tree input not set');
J
Joao Moreno 已提交
94 95
		}

J
Joao Moreno 已提交
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
		let isCollapsed: ((el: T) => boolean | undefined) | undefined;

		if (this.identityProvider) {
			isCollapsed = element => {
				const id = this.identityProvider!.getId(element).toString();
				const node = this.nodesByIdentity.get(id);

				if (!node) {
					return undefined;
				}

				return node.collapsed;
			};
		}

		this._refresh(element, isCollapsed);
J
Joao Moreno 已提交
112 113
	}

J
Joao Moreno 已提交
114 115 116 117
	resort(element: T | TInput = this.input!, recursive = true): void {
		this.model.resort((element === this.input ? null : element) as T, recursive);
	}

118 119
	// View

I
isidor 已提交
120 121 122 123 124 125 126
	refresh(element?: T): void {
		if (element === undefined) {
			this.view.rerender();
			return;
		}

		this.model.rerender(element);
127 128 129 130
	}

	// Implementation

J
Joao Moreno 已提交
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
	private _refresh(element: TInput | T, isCollapsed?: (el: T) => boolean | undefined, onDidCreateNode?: (node: ITreeNode<T, TFilterData>) => void): void {
		let onDidDeleteNode: ((node: ITreeNode<T, TFilterData>) => void) | undefined;

		if (this.identityProvider) {
			const insertedElements = new Set<string>();

			const outerOnDidCreateNode = onDidCreateNode;
			onDidCreateNode = (node: ITreeNode<T, TFilterData>) => {
				const id = this.identityProvider!.getId(node.element).toString();

				insertedElements.add(id);
				this.nodesByIdentity.set(id, node);

				if (outerOnDidCreateNode) {
					outerOnDidCreateNode(node);
				}
			};

			onDidDeleteNode = (node: ITreeNode<T, TFilterData>) => {
				const id = this.identityProvider!.getId(node.element).toString();

				if (!insertedElements.has(id)) {
					this.nodesByIdentity.delete(id);
				}
			};
		}

		this.model.setChildren((element === this.input ? null : element) as T, this.iterate(element, isCollapsed).elements, onDidCreateNode, onDidDeleteNode);
J
Joao Moreno 已提交
159 160
	}

J
Joao Moreno 已提交
161
	private iterate(element: TInput | T, isCollapsed?: (el: T) => boolean | undefined): { elements: Iterator<ITreeElement<T>>, size: number } {
J
Joao Moreno 已提交
162 163 164
		const children = this.dataSource.getChildren(element);
		const elements = Iterator.map<any, ITreeElement<T>>(Iterator.fromArray(children), element => {
			const { elements: children, size } = this.iterate(element, isCollapsed);
J
Joao Moreno 已提交
165
			const collapsible = this.dataSource.hasChildren ? this.dataSource.hasChildren(element) : undefined;
J
Joao Moreno 已提交
166
			const collapsed = size === 0 ? undefined : (isCollapsed && isCollapsed(element));
J
Joao Moreno 已提交
167

J
Joao Moreno 已提交
168
			return { element, children, collapsible, collapsed };
J
Joao Moreno 已提交
169 170 171
		});

		return { elements, size: children.length };
J
Joao Moreno 已提交
172 173
	}

J
Joao Moreno 已提交
174 175
	protected createModel(user: string, view: ISpliceable<ITreeNode<T, TFilterData>>, options: IDataTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
		return new ObjectTreeModel(user, view, options);
J
Joao Moreno 已提交
176
	}
J
Joao Moreno 已提交
177 178 179 180 181

	// view state

	getViewState(): IDataTreeViewState {
		if (!this.identityProvider) {
J
Joao Moreno 已提交
182
			throw new TreeError(this.user, 'Can\'t get tree view state without an identity provider');
J
Joao Moreno 已提交
183 184
		}

J
Joao Moreno 已提交
185
		const getId = (element: T | null) => this.identityProvider!.getId(element!).toString();
J
Joao Moreno 已提交
186 187 188
		const focus = this.getFocus().map(getId);
		const selection = this.getSelection().map(getId);

J
Joao Moreno 已提交
189
		const expanded: string[] = [];
J
Joao Moreno 已提交
190 191 192 193
		const root = this.model.getNode();
		const queue = [root];

		while (queue.length > 0) {
J
Joao Moreno 已提交
194
			const node = queue.shift()!;
J
Joao Moreno 已提交
195

J
Joao Moreno 已提交
196 197
			if (node !== root && node.collapsible && !node.collapsed) {
				expanded.push(getId(node.element!));
J
Joao Moreno 已提交
198 199 200 201 202
			}

			queue.push(...node.children);
		}

203
		return { focus, selection, expanded, scrollTop: this.scrollTop };
J
Joao Moreno 已提交
204
	}
I
isidor 已提交
205
}