asyncDataTree.ts 45.5 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 { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
7
import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
J
João Moreno 已提交
8
import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
9
import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree';
10
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
J
Joao Moreno 已提交
11
import { Emitter, Event } from 'vs/base/common/event';
J
Joao Moreno 已提交
12
import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
J
Joao Moreno 已提交
13
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
J
João Moreno 已提交
14
import { Iterable } from 'vs/base/common/iterator';
J
Joao Moreno 已提交
15 16
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
J
Joao Moreno 已提交
17
import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
M
Martin Aeschlimann 已提交
18
import { removeClasses, addClasses } from 'vs/base/browser/dom';
J
Joao Moreno 已提交
19
import { ScrollEvent } from 'vs/base/common/scrollable';
J
Joao Moreno 已提交
20
import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
21
import { IThemable } from 'vs/base/common/styler';
22
import { isFilterResult, getVisibleState } from 'vs/base/browser/ui/tree/indexTreeModel';
23
import { treeItemLoadingIcon } from 'vs/base/browser/ui/tree/treeIcons';
J
Joao Moreno 已提交
24

J
Joao Moreno 已提交
25 26 27
interface IAsyncDataTreeNode<TInput, T> {
	element: TInput | T;
	readonly parent: IAsyncDataTreeNode<TInput, T> | null;
J
Joao Moreno 已提交
28
	readonly children: IAsyncDataTreeNode<TInput, T>[];
29
	readonly id?: string | null;
30
	refreshPromise: Promise<void> | undefined;
31
	hasChildren: boolean;
J
Joao Moreno 已提交
32
	stale: boolean;
33
	slow: boolean;
34
	collapsedByDefault: boolean | undefined;
J
Joao Moreno 已提交
35 36
}

J
Joao Moreno 已提交
37 38 39 40 41 42 43 44 45 46
interface IAsyncDataTreeNodeRequiredProps<TInput, T> extends Partial<IAsyncDataTreeNode<TInput, T>> {
	readonly element: TInput | T;
	readonly parent: IAsyncDataTreeNode<TInput, T> | null;
	readonly hasChildren: boolean;
}

function createAsyncDataTreeNode<TInput, T>(props: IAsyncDataTreeNodeRequiredProps<TInput, T>): IAsyncDataTreeNode<TInput, T> {
	return {
		...props,
		children: [],
47
		refreshPromise: undefined,
J
Joao Moreno 已提交
48
		stale: true,
49 50
		slow: false,
		collapsedByDefault: undefined
J
Joao Moreno 已提交
51 52 53
	};
}

54 55 56 57 58 59 60 61 62 63
function isAncestor<TInput, T>(ancestor: IAsyncDataTreeNode<TInput, T>, descendant: IAsyncDataTreeNode<TInput, T>): boolean {
	if (!descendant.parent) {
		return false;
	} else if (descendant.parent === ancestor) {
		return true;
	} else {
		return isAncestor(ancestor, descendant.parent);
	}
}

64 65 66 67
function intersects<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, other: IAsyncDataTreeNode<TInput, T>): boolean {
	return node === other || isAncestor(node, other) || isAncestor(other, node);
}

J
Joao Moreno 已提交
68 69 70 71
interface IDataTreeListTemplateData<T> {
	templateData: T;
}

72
type AsyncDataTreeNodeMapper<TInput, T, TFilterData> = WeakMapper<ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>, ITreeNode<TInput | T, TFilterData>>;
J
Joao Moreno 已提交
73

74
class AsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<TInput | T, TFilterData> {
J
Joao Moreno 已提交
75

76 77 78 79 80 81 82 83 84 85 86
	get element(): T { return this.node.element!.element as T; }
	get children(): ITreeNode<T, TFilterData>[] { return this.node.children.map(node => new AsyncDataTreeNodeWrapper(node)); }
	get depth(): number { return this.node.depth; }
	get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
	get visibleChildIndex(): number { return this.node.visibleChildIndex; }
	get collapsible(): boolean { return this.node.collapsible; }
	get collapsed(): boolean { return this.node.collapsed; }
	get visible(): boolean { return this.node.visible; }
	get filterData(): TFilterData | undefined { return this.node.filterData; }

	constructor(private node: ITreeNode<IAsyncDataTreeNode<TInput, T> | null, TFilterData>) { }
J
Joao Moreno 已提交
87 88
}

J
Joao Moreno 已提交
89
class AsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
J
Joao Moreno 已提交
90 91

	readonly templateId: string;
J
Joao Moreno 已提交
92
	private renderedNodes = new Map<IAsyncDataTreeNode<TInput, T>, IDataTreeListTemplateData<TTemplateData>>();
J
Joao Moreno 已提交
93

J
Joao Moreno 已提交
94
	constructor(
J
Joao Moreno 已提交
95
		protected renderer: ITreeRenderer<T, TFilterData, TTemplateData>,
J
Joao Moreno 已提交
96
		protected nodeMapper: AsyncDataTreeNodeMapper<TInput, T, TFilterData>,
J
Joao Moreno 已提交
97
		readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
J
Joao Moreno 已提交
98
	) {
J
Joao Moreno 已提交
99 100 101 102 103 104 105 106
		this.templateId = renderer.templateId;
	}

	renderTemplate(container: HTMLElement): IDataTreeListTemplateData<TTemplateData> {
		const templateData = this.renderer.renderTemplate(container);
		return { templateData };
	}

107
	renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
J
Joao Moreno 已提交
108
		this.renderer.renderElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
J
Joao Moreno 已提交
109 110
	}

J
Joao Moreno 已提交
111
	renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
M
Martin Aeschlimann 已提交
112 113 114 115 116
		if (element.slow) {
			addClasses(twistieElement, treeItemLoadingIcon.classNames);
		} else {
			removeClasses(twistieElement, treeItemLoadingIcon.classNames);
		}
J
Joao Moreno 已提交
117 118 119
		return false;
	}

120
	disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
J
Joao Moreno 已提交
121
		if (this.renderer.disposeElement) {
J
Joao Moreno 已提交
122
			this.renderer.disposeElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
J
Joao Moreno 已提交
123
		}
J
Joao Moreno 已提交
124 125 126 127 128
	}

	disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
		this.renderer.disposeTemplate(templateData.templateData);
	}
J
Joao Moreno 已提交
129 130 131 132

	dispose(): void {
		this.renderedNodes.clear();
	}
J
Joao Moreno 已提交
133 134
}

J
Joao Moreno 已提交
135
function asTreeEvent<TInput, T>(e: ITreeEvent<IAsyncDataTreeNode<TInput, T> | null>): ITreeEvent<T> {
136 137
	return {
		browserEvent: e.browserEvent,
J
Joao Moreno 已提交
138
		elements: e.elements.map(e => e!.element as T)
139 140 141
	};
}

J
Joao Moreno 已提交
142
function asTreeMouseEvent<TInput, T>(e: ITreeMouseEvent<IAsyncDataTreeNode<TInput, T> | null>): ITreeMouseEvent<T> {
143 144
	return {
		browserEvent: e.browserEvent,
J
Joao Moreno 已提交
145 146
		element: e.element && e.element.element as T,
		target: e.target
147 148 149
	};
}

J
Joao Moreno 已提交
150
function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTreeNode<TInput, T> | null>): ITreeContextMenuEvent<T> {
151 152
	return {
		browserEvent: e.browserEvent,
J
Joao Moreno 已提交
153
		element: e.element && e.element.element as T,
154 155 156 157
		anchor: e.anchor
	};
}

J
Joao Moreno 已提交
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
class AsyncDataTreeElementsDragAndDropData<TInput, T, TContext> extends ElementsDragAndDropData<T, TContext> {

	set context(context: TContext | undefined) {
		this.data.context = context;
	}

	get context(): TContext | undefined {
		return this.data.context;
	}

	constructor(private data: ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>, TContext>) {
		super(data.elements.map(node => node.element as T));
	}
}

J
Joao Moreno 已提交
173 174
function asAsyncDataTreeDragAndDropData<TInput, T>(data: IDragAndDropData): IDragAndDropData {
	if (data instanceof ElementsDragAndDropData) {
J
Joao Moreno 已提交
175
		return new AsyncDataTreeElementsDragAndDropData(data);
J
Joao Moreno 已提交
176 177 178 179 180 181 182 183 184 185 186 187 188
	}

	return data;
}

class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IAsyncDataTreeNode<TInput, T>> {

	constructor(private dnd: ITreeDragAndDrop<T>) { }

	getDragURI(node: IAsyncDataTreeNode<TInput, T>): string | null {
		return this.dnd.getDragURI(node.element as T);
	}

J
Joao Moreno 已提交
189
	getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[], originalEvent: DragEvent): string | undefined {
J
Joao Moreno 已提交
190
		if (this.dnd.getDragLabel) {
J
Joao Moreno 已提交
191
			return this.dnd.getDragLabel(nodes.map(node => node.element as T), originalEvent);
J
Joao Moreno 已提交
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
		}

		return undefined;
	}

	onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
		if (this.dnd.onDragStart) {
			this.dnd.onDragStart(asAsyncDataTreeDragAndDropData(data), originalEvent);
		}
	}

	onDragOver(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent, raw = true): boolean | IListDragOverReaction {
		return this.dnd.onDragOver(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
	}

	drop(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
		this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
	}
J
Joao Moreno 已提交
210 211 212 213 214 215

	onDragEnd(originalEvent: DragEvent): void {
		if (this.dnd.onDragEnd) {
			this.dnd.onDragEnd(originalEvent);
		}
	}
J
Joao Moreno 已提交
216 217
}

J
Joao Moreno 已提交
218
function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
J
Joao Moreno 已提交
219 220
	return options && {
		...options,
J
Joao Moreno 已提交
221
		collapseByDefault: true,
J
Joao Moreno 已提交
222
		identityProvider: options.identityProvider && {
223
			getId(el) {
J
Joao Moreno 已提交
224
				return options.identityProvider!.getId(el.element as T);
225
			}
J
Joao Moreno 已提交
226
		},
J
Joao Moreno 已提交
227
		dnd: options.dnd && new AsyncDataTreeNodeListDragAndDrop(options.dnd),
J
Joao Moreno 已提交
228
		multipleSelectionController: options.multipleSelectionController && {
229
			isSelectionSingleChangeEvent(e) {
J
Joao Moreno 已提交
230
				return options.multipleSelectionController!.isSelectionSingleChangeEvent({ ...e, element: e.element } as any);
231 232
			},
			isSelectionRangeChangeEvent(e) {
J
Joao Moreno 已提交
233
				return options.multipleSelectionController!.isSelectionRangeChangeEvent({ ...e, element: e.element } as any);
234
			}
J
Joao Moreno 已提交
235 236
		},
		accessibilityProvider: options.accessibilityProvider && {
J
Joao Moreno 已提交
237
			...options.accessibilityProvider,
J
João Moreno 已提交
238 239 240 241 242 243 244 245
			getPosInSet: undefined,
			getSetSize: undefined,
			getRole: options.accessibilityProvider!.getRole ? (el) => {
				return options.accessibilityProvider!.getRole!(el.element as T);
			} : () => 'treeitem',
			isChecked: options.accessibilityProvider!.isChecked ? (e) => {
				return !!(options.accessibilityProvider?.isChecked!(e.element as T));
			} : undefined,
246
			getAriaLabel(e) {
J
Joao Moreno 已提交
247
				return options.accessibilityProvider!.getAriaLabel(e.element as T);
J
Joao Moreno 已提交
248
			},
249 250 251
			getWidgetAriaLabel() {
				return options.accessibilityProvider!.getWidgetAriaLabel();
			},
252
			getWidgetRole: options.accessibilityProvider!.getWidgetRole ? () => options.accessibilityProvider!.getWidgetRole!() : () => 'tree',
J
Joao Moreno 已提交
253 254 255 256 257 258
			getAriaLevel: options.accessibilityProvider!.getAriaLevel && (node => {
				return options.accessibilityProvider!.getAriaLevel!(node.element as T);
			}),
			getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => {
				return options.accessibilityProvider!.getActiveDescendantId!(node.element as T);
			})
J
Joao Moreno 已提交
259 260
		},
		filter: options.filter && {
J
Joao Moreno 已提交
261
			filter(e, parentVisibility) {
J
Joao Moreno 已提交
262
				return options.filter!.filter(e.element as T, parentVisibility);
J
Joao Moreno 已提交
263 264
			}
		},
J
Joao Moreno 已提交
265
		keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && {
J
Joao Moreno 已提交
266
			...options.keyboardNavigationLabelProvider,
J
Joao Moreno 已提交
267
			getKeyboardNavigationLabel(e) {
J
Joao Moreno 已提交
268
				return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e.element as T);
269
			}
J
Joao Moreno 已提交
270
		},
271 272 273 274 275
		sorter: undefined,
		expandOnlyOnTwistieClick: typeof options.expandOnlyOnTwistieClick === 'undefined' ? undefined : (
			typeof options.expandOnlyOnTwistieClick !== 'function' ? options.expandOnlyOnTwistieClick : (
				e => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T)
			)
J
Joao Moreno 已提交
276
		),
I
isidor 已提交
277
		additionalScrollHeight: options.additionalScrollHeight
278 279 280
	};
}

J
Joao Moreno 已提交
281 282
export interface IAsyncDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate { }

283 284 285 286 287
export interface IAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptionsUpdate, Pick<IAbstractTreeOptions<T, TFilterData>, Exclude<keyof IAbstractTreeOptions<T, TFilterData>, 'collapseByDefault'>> {
	readonly collapseByDefault?: { (e: T): boolean; };
	readonly identityProvider?: IIdentityProvider<T>;
	readonly sorter?: ITreeSorter<T>;
	readonly autoExpandSingleChildren?: boolean;
288
}
289

290
export interface IAsyncDataTreeViewState {
J
Joao Moreno 已提交
291 292 293 294
	readonly focus?: string[];
	readonly selection?: string[];
	readonly expanded?: string[];
	readonly scrollTop?: number;
295 296 297 298 299 300 301 302
}

interface IAsyncDataTreeViewStateContext<TInput, T> {
	readonly viewState: IAsyncDataTreeViewState;
	readonly selection: IAsyncDataTreeNode<TInput, T>[];
	readonly focus: IAsyncDataTreeNode<TInput, T>[];
}

303 304 305 306 307
function dfs<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, fn: (node: IAsyncDataTreeNode<TInput, T>) => void): void {
	fn(node);
	node.children.forEach(child => dfs(child, fn));
}

308
export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable, IThemable {
J
Joao Moreno 已提交
309

310
	protected readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
J
Joao Moreno 已提交
311
	protected readonly root: IAsyncDataTreeNode<TInput, T>;
J
rename  
Joao Moreno 已提交
312
	private readonly nodes = new Map<null | T, IAsyncDataTreeNode<TInput, T>>();
J
Joao Moreno 已提交
313
	private readonly sorter?: ITreeSorter<T>;
314
	private readonly collapseByDefault?: { (e: T): boolean; };
315

316
	private readonly subTreeRefreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, Promise<void>>();
J
João Moreno 已提交
317
	private readonly refreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, CancelablePromise<Iterable<T>>>();
318

319
	protected readonly identityProvider?: IIdentityProvider<T>;
J
Joao Moreno 已提交
320
	private readonly autoExpandSingleChildren: boolean;
J
Joao Moreno 已提交
321

322
	private readonly _onDidRender = new Emitter<void>();
J
Joao Moreno 已提交
323
	protected readonly _onDidChangeNodeSlowState = new Emitter<IAsyncDataTreeNode<TInput, T>>();
J
Joao Moreno 已提交
324

325
	protected readonly nodeMapper: AsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new AsyncDataTreeNodeWrapper(node));
326

327
	protected readonly disposables = new DisposableStore();
J
Joao Moreno 已提交
328

J
Joao Moreno 已提交
329
	get onDidScroll(): Event<ScrollEvent> { return this.tree.onDidScroll; }
J
Joao Moreno 已提交
330

J
Joao Moreno 已提交
331 332
	get onDidChangeFocus(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidChangeFocus, asTreeEvent); }
	get onDidChangeSelection(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidChangeSelection, asTreeEvent); }
J
Joao Moreno 已提交
333
	get onDidOpen(): Event<ITreeEvent<T>> { return Event.map(this.tree.onDidOpen, asTreeEvent); }
334

335
	get onKeyDown(): Event<KeyboardEvent> { return this.tree.onKeyDown; }
J
Joao Moreno 已提交
336 337 338
	get onMouseClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onMouseClick, asTreeMouseEvent); }
	get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.tree.onMouseDblClick, asTreeMouseEvent); }
	get onContextMenu(): Event<ITreeContextMenuEvent<T>> { return Event.map(this.tree.onContextMenu, asTreeContextMenuEvent); }
J
Joao Moreno 已提交
339 340
	get onDidFocus(): Event<void> { return this.tree.onDidFocus; }
	get onDidBlur(): Event<void> { return this.tree.onDidBlur; }
341

342
	get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T> | null, TFilterData>> { return this.tree.onDidChangeCollapseState; }
343

344 345
	get onDidUpdateOptions(): Event<IAsyncDataTreeOptionsUpdate> { return this.tree.onDidUpdateOptions; }

J
Joao Moreno 已提交
346
	get filterOnType(): boolean { return this.tree.filterOnType; }
J
Joao Moreno 已提交
347
	get openOnSingleClick(): boolean { return this.tree.openOnSingleClick; }
348 349 350 351 352 353 354 355
	get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) {
		if (typeof this.tree.expandOnlyOnTwistieClick === 'boolean') {
			return this.tree.expandOnlyOnTwistieClick;
		}

		const fn = this.tree.expandOnlyOnTwistieClick;
		return element => fn(this.nodes.get((element === this.root.element ? null : element) as T) || null);
	}
J
Joao Moreno 已提交
356

357 358
	get onDidDispose(): Event<void> { return this.tree.onDidDispose; }

J
Joao Moreno 已提交
359
	constructor(
360
		protected user: string,
J
Joao Moreno 已提交
361
		container: HTMLElement,
J
Joao Moreno 已提交
362
		delegate: IListVirtualDelegate<T>,
363
		renderers: ITreeRenderer<T, TFilterData, any>[],
J
Joao Moreno 已提交
364
		private dataSource: IAsyncDataSource<TInput, T>,
J
Joao Moreno 已提交
365
		options: IAsyncDataTreeOptions<T, TFilterData> = {}
J
Joao Moreno 已提交
366
	) {
J
Joao Moreno 已提交
367 368
		this.identityProvider = options.identityProvider;
		this.autoExpandSingleChildren = typeof options.autoExpandSingleChildren === 'undefined' ? false : options.autoExpandSingleChildren;
J
Joao Moreno 已提交
369
		this.sorter = options.sorter;
370
		this.collapseByDefault = options.collapseByDefault;
371

J
Joao Moreno 已提交
372
		this.tree = this.createTree(user, container, delegate, renderers, options);
373

J
Joao Moreno 已提交
374
		this.root = createAsyncDataTreeNode({
J
Joao Moreno 已提交
375
			element: undefined!,
J
Joao Moreno 已提交
376
			parent: null,
J
Joao Moreno 已提交
377 378
			hasChildren: true
		});
J
Joao Moreno 已提交
379

380 381 382
		if (this.identityProvider) {
			this.root = {
				...this.root,
J
Joao Moreno 已提交
383
				id: null
384 385 386
			};
		}

J
rename  
Joao Moreno 已提交
387
		this.nodes.set(null, this.root);
J
Joao Moreno 已提交
388

389
		this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables);
J
Joao Moreno 已提交
390 391
	}

J
Joao Moreno 已提交
392 393 394 395 396 397 398 399
	protected createTree(
		user: string,
		container: HTMLElement,
		delegate: IListVirtualDelegate<T>,
		renderers: ITreeRenderer<T, TFilterData, any>[],
		options: IAsyncDataTreeOptions<T, TFilterData>
	): ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData> {
		const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
J
Joao Moreno 已提交
400
		const objectTreeRenderers = renderers.map(r => new AsyncDataTreeRenderer(r, this.nodeMapper, this._onDidChangeNodeSlowState.event));
J
Joao Moreno 已提交
401 402 403 404 405
		const objectTreeOptions = asObjectTreeOptions<TInput, T, TFilterData>(options) || {};

		return new ObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
	}

J
Joao Moreno 已提交
406 407 408 409
	updateOptions(options: IAsyncDataTreeOptionsUpdate = {}): void {
		this.tree.updateOptions(options);
	}

410 411 412 413
	get options(): IAsyncDataTreeOptions<T, TFilterData> {
		return this.tree.options as IAsyncDataTreeOptions<T, TFilterData>;
	}

J
Joao Moreno 已提交
414 415 416 417 418 419
	// Widget

	getHTMLElement(): HTMLElement {
		return this.tree.getHTMLElement();
	}

J
Joao Moreno 已提交
420 421 422 423 424 425 426 427
	get contentHeight(): number {
		return this.tree.contentHeight;
	}

	get onDidChangeContentHeight(): Event<number> {
		return this.tree.onDidChangeContentHeight;
	}

428 429 430 431 432 433 434 435
	get scrollTop(): number {
		return this.tree.scrollTop;
	}

	set scrollTop(scrollTop: number) {
		this.tree.scrollTop = scrollTop;
	}

I
isidor 已提交
436 437 438 439 440 441 442 443
	get scrollLeft(): number {
		return this.tree.scrollLeft;
	}

	set scrollLeft(scrollLeft: number) {
		this.tree.scrollLeft = scrollLeft;
	}

444 445 446 447
	get scrollHeight(): number {
		return this.tree.scrollHeight;
	}

I
isidor 已提交
448 449 450 451
	get renderHeight(): number {
		return this.tree.renderHeight;
	}

J
Joao Moreno 已提交
452 453 454 455
	get lastVisibleElement(): T {
		return this.tree.lastVisibleElement!.element as T;
	}

456 457 458 459 460 461 462 463
	get ariaLabel(): string {
		return this.tree.ariaLabel;
	}

	set ariaLabel(value: string) {
		this.tree.ariaLabel = value;
	}

I
isidor 已提交
464 465 466 467
	domFocus(): void {
		this.tree.domFocus();
	}

468 469
	layout(height?: number, width?: number): void {
		this.tree.layout(height, width);
J
Joao Moreno 已提交
470 471
	}

J
Joao Moreno 已提交
472 473
	style(styles: IListStyles): void {
		this.tree.style(styles);
J
Joao Moreno 已提交
474 475
	}

476
	// Model
J
Joao Moreno 已提交
477

J
Joao Moreno 已提交
478 479 480 481
	getInput(): TInput | undefined {
		return this.root.element as TInput;
	}

482
	async setInput(input: TInput, viewState?: IAsyncDataTreeViewState): Promise<void> {
483 484 485
		this.refreshPromises.forEach(promise => promise.cancel());
		this.refreshPromises.clear();

J
Joao Moreno 已提交
486
		this.root.element = input!;
487 488 489

		const viewStateContext = viewState && { viewState, focus: [], selection: [] } as IAsyncDataTreeViewStateContext<TInput, T>;

J
Joao Moreno 已提交
490
		await this._updateChildren(input, true, false, viewStateContext);
491 492 493 494 495

		if (viewStateContext) {
			this.tree.setFocus(viewStateContext.focus);
			this.tree.setSelection(viewStateContext.selection);
		}
J
Joao Moreno 已提交
496 497 498 499

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

J
Joao Moreno 已提交
502 503
	async updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false): Promise<void> {
		await this._updateChildren(element, recursive, rerender);
J
wip  
Joao Moreno 已提交
504 505
	}

J
Joao Moreno 已提交
506
	private async _updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
J
Joao Moreno 已提交
507
		if (typeof this.root.element === 'undefined') {
J
Joao Moreno 已提交
508
			throw new TreeError(this.user, 'Tree input not set');
J
Joao Moreno 已提交
509 510
		}

511 512
		if (this.root.refreshPromise) {
			await this.root.refreshPromise;
513 514 515
			await Event.toPromise(this._onDidRender.event);
		}

J
Joao Moreno 已提交
516 517 518 519
		const node = this.getDataNode(element);
		await this.refreshAndRenderNode(node, recursive, viewStateContext);

		if (rerender) {
J
Joao Moreno 已提交
520 521 522 523 524 525
			try {
				this.tree.rerender(node);
			} catch {
				// missing nodes are fine, this could've resulted from
				// parallel refresh calls, removing `node` altogether
			}
J
Joao Moreno 已提交
526
		}
J
Joao Moreno 已提交
527 528
	}

J
Joao Moreno 已提交
529 530 531 532
	resort(element: TInput | T = this.root.element, recursive = true): void {
		this.tree.resort(this.getDataNode(element), recursive);
	}

J
Joao Moreno 已提交
533 534
	hasNode(element: TInput | T): boolean {
		return element === this.root.element || this.nodes.has(element as T);
J
Joao Moreno 已提交
535 536
	}

537 538
	// View

I
isidor 已提交
539
	rerender(element?: T): void {
A
Alex Ross 已提交
540
		if (element === undefined || element === this.root.element) {
I
isidor 已提交
541 542 543 544
			this.tree.rerender();
			return;
		}

545
		const node = this.getDataNode(element);
I
isidor 已提交
546
		this.tree.rerender(node);
547 548
	}

J
Joao Moreno 已提交
549 550 551 552 553
	updateWidth(element: T): void {
		const node = this.getDataNode(element);
		this.tree.updateWidth(node);
	}

J
Joao Moreno 已提交
554 555
	// Tree

J
Joao Moreno 已提交
556
	getNode(element: TInput | T = this.root.element): ITreeNode<TInput | T, TFilterData> {
J
Joao Moreno 已提交
557 558
		const dataNode = this.getDataNode(element);
		const node = this.tree.getNode(dataNode === this.root ? null : dataNode);
J
Joao Moreno 已提交
559
		return this.nodeMapper.map(node);
I
isidor 已提交
560 561
	}

562
	collapse(element: T, recursive: boolean = false): boolean {
563 564
		const node = this.getDataNode(element);
		return this.tree.collapse(node === this.root ? null : node, recursive);
J
Joao Moreno 已提交
565 566
	}

567
	async expand(element: T, recursive: boolean = false): Promise<boolean> {
568
		if (typeof this.root.element === 'undefined') {
J
Joao Moreno 已提交
569
			throw new TreeError(this.user, 'Tree input not set');
570 571
		}

572 573
		if (this.root.refreshPromise) {
			await this.root.refreshPromise;
574 575 576
			await Event.toPromise(this._onDidRender.event);
		}

J
Joao Moreno 已提交
577
		const node = this.getDataNode(element);
J
Joao Moreno 已提交
578

J
Joao Moreno 已提交
579
		if (this.tree.hasElement(node) && !this.tree.isCollapsible(node)) {
J
Joao Moreno 已提交
580 581 582
			return false;
		}

583 584 585 586 587 588
		if (node.refreshPromise) {
			await this.root.refreshPromise;
			await Event.toPromise(this._onDidRender.event);
		}

		if (node !== this.root && !node.refreshPromise && !this.tree.isCollapsed(node)) {
J
Joao Moreno 已提交
589
			return false;
J
Joao Moreno 已提交
590 591
		}

592
		const result = this.tree.expand(node === this.root ? null : node, recursive);
J
Joao Moreno 已提交
593

594 595
		if (node.refreshPromise) {
			await this.root.refreshPromise;
596
			await Event.toPromise(this._onDidRender.event);
J
Joao Moreno 已提交
597 598
		}

599
		return result;
J
Joao Moreno 已提交
600 601
	}

602 603
	toggleCollapsed(element: T, recursive: boolean = false): boolean {
		return this.tree.toggleCollapsed(this.getDataNode(element), recursive);
J
Joao Moreno 已提交
604 605
	}

J
Joao Moreno 已提交
606 607 608 609
	expandAll(): void {
		this.tree.expandAll();
	}

J
Joao Moreno 已提交
610 611 612 613
	collapseAll(): void {
		this.tree.collapseAll();
	}

614 615 616 617
	isCollapsible(element: T): boolean {
		return this.tree.isCollapsible(this.getDataNode(element));
	}

J
Joao Moreno 已提交
618
	isCollapsed(element: T): boolean {
J
Joao Moreno 已提交
619
		return this.tree.isCollapsed(this.getDataNode(element));
J
Joao Moreno 已提交
620 621
	}

J
Joao Moreno 已提交
622 623 624 625
	toggleKeyboardNavigation(): void {
		this.tree.toggleKeyboardNavigation();
	}

J
Joao Moreno 已提交
626 627 628 629 630
	refilter(): void {
		this.tree.refilter();
	}

	setSelection(elements: T[], browserEvent?: UIEvent): void {
J
Joao Moreno 已提交
631
		const nodes = elements.map(e => this.getDataNode(e));
J
Joao Moreno 已提交
632 633 634 635 636
		this.tree.setSelection(nodes, browserEvent);
	}

	getSelection(): T[] {
		const nodes = this.tree.getSelection();
J
Joao Moreno 已提交
637
		return nodes.map(n => n!.element as T);
J
Joao Moreno 已提交
638 639 640
	}

	setFocus(elements: T[], browserEvent?: UIEvent): void {
J
Joao Moreno 已提交
641
		const nodes = elements.map(e => this.getDataNode(e));
J
Joao Moreno 已提交
642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
		this.tree.setFocus(nodes, browserEvent);
	}

	focusNext(n = 1, loop = false, browserEvent?: UIEvent): void {
		this.tree.focusNext(n, loop, browserEvent);
	}

	focusPrevious(n = 1, loop = false, browserEvent?: UIEvent): void {
		this.tree.focusPrevious(n, loop, browserEvent);
	}

	focusNextPage(browserEvent?: UIEvent): void {
		this.tree.focusNextPage(browserEvent);
	}

	focusPreviousPage(browserEvent?: UIEvent): void {
		this.tree.focusPreviousPage(browserEvent);
	}

	focusLast(browserEvent?: UIEvent): void {
		this.tree.focusLast(browserEvent);
	}

	focusFirst(browserEvent?: UIEvent): void {
		this.tree.focusFirst(browserEvent);
	}

	getFocus(): T[] {
		const nodes = this.tree.getFocus();
J
Joao Moreno 已提交
671
		return nodes.map(n => n!.element as T);
J
Joao Moreno 已提交
672 673
	}

674
	open(elements: T[], browserEvent?: UIEvent): void {
J
Joao Moreno 已提交
675
		const nodes = elements.map(e => this.getDataNode(e));
676
		this.tree.open(nodes, browserEvent);
J
Joao Moreno 已提交
677 678 679
	}

	reveal(element: T, relativeTop?: number): void {
J
Joao Moreno 已提交
680
		this.tree.reveal(this.getDataNode(element), relativeTop);
J
Joao Moreno 已提交
681 682 683
	}

	getRelativeTop(element: T): number | null {
J
Joao Moreno 已提交
684
		return this.tree.getRelativeTop(this.getDataNode(element));
J
Joao Moreno 已提交
685 686
	}

J
Joao Moreno 已提交
687 688
	// Tree navigation

J
Joao Moreno 已提交
689
	getParentElement(element: T): TInput | T {
J
Joao Moreno 已提交
690
		const node = this.tree.getParentElement(this.getDataNode(element));
J
Joao Moreno 已提交
691
		return (node && node.element)!;
J
Joao Moreno 已提交
692 693
	}

J
Joao Moreno 已提交
694
	getFirstElementChild(element: TInput | T = this.root.element): TInput | T | undefined {
J
Joao Moreno 已提交
695 696
		const dataNode = this.getDataNode(element);
		const node = this.tree.getFirstElementChild(dataNode === this.root ? null : dataNode);
J
Joao Moreno 已提交
697
		return (node && node.element)!;
J
Joao Moreno 已提交
698 699 700 701
	}

	// Implementation

J
Joao Moreno 已提交
702
	private getDataNode(element: TInput | T): IAsyncDataTreeNode<TInput, T> {
J
rename  
Joao Moreno 已提交
703
		const node: IAsyncDataTreeNode<TInput, T> | undefined = this.nodes.get((element === this.root.element ? null : element) as T);
J
Joao Moreno 已提交
704

705
		if (!node) {
J
Joao Moreno 已提交
706
			throw new TreeError(this.user, `Data tree node not found: ${element}`);
J
Joao Moreno 已提交
707 708 709 710 711
		}

		return node;
	}

712
	private async refreshAndRenderNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
713 714
		await this.refreshNode(node, recursive, viewStateContext);
		this.render(node, viewStateContext);
J
Joao Moreno 已提交
715 716
	}

717
	private async refreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
718 719
		let result: Promise<void> | undefined;

720
		this.subTreeRefreshPromises.forEach((refreshPromise, refreshNode) => {
721
			if (!result && intersects(refreshNode, node)) {
722
				result = refreshPromise.then(() => this.refreshNode(node, recursive, viewStateContext));
723 724 725 726 727 728 729
			}
		});

		if (result) {
			return result;
		}

730
		return this.doRefreshSubTree(node, recursive, viewStateContext);
731 732
	}

733
	private async doRefreshSubTree(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
734 735 736 737 738 739 740 741
		let done: () => void;
		node.refreshPromise = new Promise(c => done = c);
		this.subTreeRefreshPromises.set(node, node.refreshPromise);

		node.refreshPromise.finally(() => {
			node.refreshPromise = undefined;
			this.subTreeRefreshPromises.delete(node);
		});
J
Joao Moreno 已提交
742

743
		try {
J
Joao Moreno 已提交
744 745
			const childrenToRefresh = await this.doRefreshNode(node, recursive, viewStateContext);
			node.stale = false;
746

J
Joao Moreno 已提交
747
			await Promise.all(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext)));
748
		} finally {
749
			done!();
J
Joao Moreno 已提交
750
		}
751
	}
J
Joao Moreno 已提交
752

J
Joao Moreno 已提交
753
	private async doRefreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<IAsyncDataTreeNode<TInput, T>[]> {
754
		node.hasChildren = !!this.dataSource.hasChildren(node.element!);
J
Joao Moreno 已提交
755

J
João Moreno 已提交
756
		let childrenPromise: Promise<Iterable<T>>;
J
Joao Moreno 已提交
757

758
		if (!node.hasChildren) {
J
João Moreno 已提交
759
			childrenPromise = Promise.resolve(Iterable.empty());
760 761
		} else {
			const slowTimeout = timeout(800);
J
Joao Moreno 已提交
762

763 764 765 766
			slowTimeout.then(() => {
				node.slow = true;
				this._onDidChangeNodeSlowState.fire(node);
			}, _ => null);
J
Joao Moreno 已提交
767

J
Joao Moreno 已提交
768 769
			childrenPromise = this.doGetChildren(node)
				.finally(() => slowTimeout.cancel());
770
		}
J
Joao Moreno 已提交
771

772 773
		try {
			const children = await childrenPromise;
J
Joao Moreno 已提交
774
			return this.setChildren(node, children, recursive, viewStateContext);
J
Joao Moreno 已提交
775 776 777 778
		} catch (err) {
			if (node !== this.root) {
				this.tree.collapse(node === this.root ? null : node);
			}
J
Joao Moreno 已提交
779

J
Joao Moreno 已提交
780
			if (isPromiseCanceledError(err)) {
J
Joao Moreno 已提交
781
				return [];
J
Joao Moreno 已提交
782 783
			}

J
Joao Moreno 已提交
784
			throw err;
785 786 787 788 789
		} finally {
			if (node.slow) {
				node.slow = false;
				this._onDidChangeNodeSlowState.fire(node);
			}
J
Joao Moreno 已提交
790 791 792
		}
	}

J
João Moreno 已提交
793
	private doGetChildren(node: IAsyncDataTreeNode<TInput, T>): Promise<Iterable<T>> {
794 795 796 797 798 799
		let result = this.refreshPromises.get(node);

		if (result) {
			return result;
		}

J
Joao Moreno 已提交
800 801
		result = createCancelablePromise(async () => {
			const children = await this.dataSource.getChildren(node.element!);
802
			return this.processChildren(children);
J
Joao Moreno 已提交
803
		});
J
Joao Moreno 已提交
804

805
		this.refreshPromises.set(node, result);
J
Joao Moreno 已提交
806

807
		return result.finally(() => { this.refreshPromises.delete(node); });
808 809
	}

J
Joao Moreno 已提交
810 811 812 813 814
	private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T> | null, any>): void {
		if (node.element === null) {
			return;
		}

J
Joao Moreno 已提交
815
		if (!node.collapsed && node.element.stale) {
816
			if (deep) {
J
Joao Moreno 已提交
817
				this.collapse(node.element.element as T);
818
			} else {
819
				this.refreshAndRenderNode(node.element, false)
J
Joao Moreno 已提交
820
					.catch(onUnexpectedError);
821
			}
J
Joao Moreno 已提交
822 823 824
		}
	}

J
João Moreno 已提交
825 826 827
	private setChildren(node: IAsyncDataTreeNode<TInput, T>, childrenElementsIterable: Iterable<T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): IAsyncDataTreeNode<TInput, T>[] {
		const childrenElements = [...childrenElementsIterable];

J
Joao Moreno 已提交
828 829 830 831 832
		// perf: if the node was and still is a leaf, avoid all this hassle
		if (node.children.length === 0 && childrenElements.length === 0) {
			return [];
		}

833
		const nodesToForget = new Map<T, IAsyncDataTreeNode<TInput, T>>();
834
		const childrenTreeNodesById = new Map<string, { node: IAsyncDataTreeNode<TInput, T>, collapsed: boolean }>();
J
Joao Moreno 已提交
835

836
		for (const child of node.children) {
837 838 839
			nodesToForget.set(child.element as T, child);

			if (this.identityProvider) {
840 841
				const collapsed = this.tree.isCollapsed(child);
				childrenTreeNodesById.set(child.id!, { node: child, collapsed });
842
			}
J
Joao Moreno 已提交
843 844
		}

845
		const childrenToRefresh: IAsyncDataTreeNode<TInput, T>[] = [];
J
Joao Moreno 已提交
846

847
		const children = childrenElements.map<IAsyncDataTreeNode<TInput, T>>(element => {
848 849
			const hasChildren = !!this.dataSource.hasChildren(element);

850
			if (!this.identityProvider) {
851 852 853 854 855 856 857 858
				const asyncDataTreeNode = createAsyncDataTreeNode({ element, parent: node, hasChildren });

				if (hasChildren && this.collapseByDefault && !this.collapseByDefault(element)) {
					asyncDataTreeNode.collapsedByDefault = false;
					childrenToRefresh.push(asyncDataTreeNode);
				}

				return asyncDataTreeNode;
859 860 861
			}

			const id = this.identityProvider.getId(element).toString();
862
			const result = childrenTreeNodesById.get(id);
863

864 865
			if (result) {
				const asyncDataTreeNode = result.node;
866 867 868 869

				nodesToForget.delete(asyncDataTreeNode.element as T);
				this.nodes.delete(asyncDataTreeNode.element as T);
				this.nodes.set(element, asyncDataTreeNode);
870

871
				asyncDataTreeNode.element = element;
872
				asyncDataTreeNode.hasChildren = hasChildren;
873

J
Joao Moreno 已提交
874
				if (recursive) {
875
					if (result.collapsed) {
876
						asyncDataTreeNode.children.forEach(node => dfs(node, node => this.nodes.delete(node.element as T)));
877 878
						asyncDataTreeNode.children.splice(0, asyncDataTreeNode.children.length);
						asyncDataTreeNode.stale = true;
J
Joao Moreno 已提交
879 880 881
					} else {
						childrenToRefresh.push(asyncDataTreeNode);
					}
882 883 884
				} else if (hasChildren && this.collapseByDefault && !this.collapseByDefault(element)) {
					asyncDataTreeNode.collapsedByDefault = false;
					childrenToRefresh.push(asyncDataTreeNode);
885 886
				}

J
Joao Moreno 已提交
887
				return asyncDataTreeNode;
888 889
			}

890
			const childAsyncDataTreeNode = createAsyncDataTreeNode({ element, parent: node, id, hasChildren });
J
Joao Moreno 已提交
891

892 893 894
			if (viewStateContext && viewStateContext.viewState.focus && viewStateContext.viewState.focus.indexOf(id) > -1) {
				viewStateContext.focus.push(childAsyncDataTreeNode);
			}
895

896 897 898
			if (viewStateContext && viewStateContext.viewState.selection && viewStateContext.viewState.selection.indexOf(id) > -1) {
				viewStateContext.selection.push(childAsyncDataTreeNode);
			}
899

900 901
			if (viewStateContext && viewStateContext.viewState.expanded && viewStateContext.viewState.expanded.indexOf(id) > -1) {
				childrenToRefresh.push(childAsyncDataTreeNode);
902 903 904
			} else if (hasChildren && this.collapseByDefault && !this.collapseByDefault(element)) {
				childAsyncDataTreeNode.collapsedByDefault = false;
				childrenToRefresh.push(childAsyncDataTreeNode);
J
Joao Moreno 已提交
905
			}
906

J
Joao Moreno 已提交
907 908
			return childAsyncDataTreeNode;
		});
909

J
João Moreno 已提交
910
		for (const node of nodesToForget.values()) {
911 912 913 914 915 916 917
			dfs(node, node => this.nodes.delete(node.element as T));
		}

		for (const child of children) {
			this.nodes.set(child.element as T, child);
		}

918
		node.children.splice(0, node.children.length, ...children);
J
Joao Moreno 已提交
919

920 921 922 923 924 925
		// TODO@joao this doesn't take filter into account
		if (node !== this.root && this.autoExpandSingleChildren && children.length === 1 && childrenToRefresh.length === 0) {
			children[0].collapsedByDefault = false;
			childrenToRefresh.push(children[0]);
		}

J
Joao Moreno 已提交
926
		return childrenToRefresh;
927
	}
928

J
Joao Moreno 已提交
929
	protected render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
J
Joao Moreno 已提交
930
		const children = node.children.map(node => this.asTreeElement(node, viewStateContext));
J
Joao Moreno 已提交
931
		this.tree.setChildren(node === this.root ? null : node, children);
J
Joao Moreno 已提交
932 933 934 935 936

		if (node !== this.root) {
			this.tree.setCollapsible(node, node.hasChildren);
		}

937
		this._onDidRender.fire();
J
Joao Moreno 已提交
938 939
	}

J
Joao Moreno 已提交
940
	protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> {
941 942 943 944 945 946 947 948
		if (node.stale) {
			return {
				element: node,
				collapsible: node.hasChildren,
				collapsed: true
			};
		}

J
Joao Moreno 已提交
949 950 951 952 953 954 955 956 957 958 959 960
		let collapsed: boolean | undefined;

		if (viewStateContext && viewStateContext.viewState.expanded && node.id && viewStateContext.viewState.expanded.indexOf(node.id) > -1) {
			collapsed = false;
		} else {
			collapsed = node.collapsedByDefault;
		}

		node.collapsedByDefault = undefined;

		return {
			element: node,
J
João Moreno 已提交
961
			children: node.hasChildren ? Iterable.map(node.children, child => this.asTreeElement(child, viewStateContext)) : [],
J
Joao Moreno 已提交
962 963 964 965 966
			collapsible: node.hasChildren,
			collapsed
		};
	}

J
João Moreno 已提交
967
	protected processChildren(children: Iterable<T>): Iterable<T> {
968
		if (this.sorter) {
J
João Moreno 已提交
969
			children = [...children].sort(this.sorter.compare.bind(this.sorter));
970 971 972 973 974
		}

		return children;
	}

975 976 977 978
	// view state

	getViewState(): IAsyncDataTreeViewState {
		if (!this.identityProvider) {
J
Joao Moreno 已提交
979
			throw new TreeError(this.user, 'Can\'t get tree view state without an identity provider');
980 981 982 983 984 985 986 987 988 989 990 991 992
		}

		const getId = (element: T) => this.identityProvider!.getId(element).toString();
		const focus = this.getFocus().map(getId);
		const selection = this.getSelection().map(getId);

		const expanded: string[] = [];
		const root = this.tree.getNode();
		const queue = [root];

		while (queue.length > 0) {
			const node = queue.shift()!;

993
			if (node !== root && node.collapsible && !node.collapsed) {
994 995 996 997 998 999
				expanded.push(getId(node.element!.element as T));
			}

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

J
Joao Moreno 已提交
1000
		return { focus, selection, expanded, scrollTop: this.scrollTop };
1001 1002
	}

J
Joao Moreno 已提交
1003
	dispose(): void {
1004
		this.disposables.dispose();
J
Joao Moreno 已提交
1005
	}
1006
}
J
Joao Moreno 已提交
1007

J
Joao Moreno 已提交
1008
type CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = WeakMapper<ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, ITreeNode<ICompressedTreeNode<TInput | T>, TFilterData>>;
J
Joao Moreno 已提交
1009

J
Joao Moreno 已提交
1010
class CompressibleAsyncDataTreeNodeWrapper<TInput, T, TFilterData> implements ITreeNode<ICompressedTreeNode<TInput | T>, TFilterData> {
J
Joao Moreno 已提交
1011

J
Joao Moreno 已提交
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031
	get element(): ICompressedTreeNode<TInput | T> {
		return {
			elements: this.node.element.elements.map(e => e.element),
			incompressible: this.node.element.incompressible
		};
	}

	get children(): ITreeNode<ICompressedTreeNode<TInput | T>, TFilterData>[] { return this.node.children.map(node => new CompressibleAsyncDataTreeNodeWrapper(node)); }
	get depth(): number { return this.node.depth; }
	get visibleChildrenCount(): number { return this.node.visibleChildrenCount; }
	get visibleChildIndex(): number { return this.node.visibleChildIndex; }
	get collapsible(): boolean { return this.node.collapsible; }
	get collapsed(): boolean { return this.node.collapsed; }
	get visible(): boolean { return this.node.visible; }
	get filterData(): TFilterData | undefined { return this.node.filterData; }

	constructor(private node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>) { }
}

class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ICompressibleTreeRenderer<IAsyncDataTreeNode<TInput, T>, TFilterData, IDataTreeListTemplateData<TTemplateData>> {
J
Joao Moreno 已提交
1032

J
Joao Moreno 已提交
1033 1034 1035
	readonly templateId: string;
	private renderedNodes = new Map<IAsyncDataTreeNode<TInput, T>, IDataTreeListTemplateData<TTemplateData>>();
	private disposables: IDisposable[] = [];
J
Joao Moreno 已提交
1036 1037 1038

	constructor(
		protected renderer: ICompressibleTreeRenderer<T, TFilterData, TTemplateData>,
J
Joao Moreno 已提交
1039
		protected nodeMapper: AsyncDataTreeNodeMapper<TInput, T, TFilterData>,
J
Joao Moreno 已提交
1040
		private compressibleNodeMapperProvider: () => CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData>,
J
Joao Moreno 已提交
1041
		readonly onDidChangeTwistieState: Event<IAsyncDataTreeNode<TInput, T>>
J
Joao Moreno 已提交
1042
	) {
J
Joao Moreno 已提交
1043 1044 1045 1046 1047 1048 1049 1050 1051 1052
		this.templateId = renderer.templateId;
	}

	renderTemplate(container: HTMLElement): IDataTreeListTemplateData<TTemplateData> {
		const templateData = this.renderer.renderTemplate(container);
		return { templateData };
	}

	renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
		this.renderer.renderElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
J
Joao Moreno 已提交
1053 1054
	}

J
Joao Moreno 已提交
1055
	renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
J
Joao Moreno 已提交
1056
		this.renderer.renderCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
J
Joao Moreno 已提交
1057 1058 1059
	}

	renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
M
Martin Aeschlimann 已提交
1060 1061 1062 1063 1064
		if (element.slow) {
			addClasses(twistieElement, treeItemLoadingIcon.classNames);
		} else {
			removeClasses(twistieElement, treeItemLoadingIcon.classNames);
		}
J
Joao Moreno 已提交
1065 1066 1067 1068 1069 1070 1071 1072 1073
		return false;
	}

	disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
		if (this.renderer.disposeElement) {
			this.renderer.disposeElement(this.nodeMapper.map(node) as ITreeNode<T, TFilterData>, index, templateData.templateData, height);
		}
	}

1074 1075 1076 1077 1078 1079
	disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
		if (this.renderer.disposeCompressedElements) {
			this.renderer.disposeCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
		}
	}

J
Joao Moreno 已提交
1080 1081 1082 1083 1084 1085 1086
	disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
		this.renderer.disposeTemplate(templateData.templateData);
	}

	dispose(): void {
		this.renderedNodes.clear();
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
1087 1088 1089
	}
}

J
Joao Moreno 已提交
1090 1091 1092 1093
export interface ITreeCompressionDelegate<T> {
	isIncompressible(element: T): boolean;
}

1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
function asCompressibleObjectTreeOptions<TInput, T, TFilterData>(options?: ICompressibleAsyncDataTreeOptions<T, TFilterData>): ICompressibleObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
	const objectTreeOptions = options && asObjectTreeOptions(options);

	return objectTreeOptions && {
		...objectTreeOptions,
		keyboardNavigationLabelProvider: objectTreeOptions.keyboardNavigationLabelProvider && {
			...objectTreeOptions.keyboardNavigationLabelProvider,
			getCompressedNodeKeyboardNavigationLabel(els) {
				return options!.keyboardNavigationLabelProvider!.getCompressedNodeKeyboardNavigationLabel(els.map(e => e.element as T));
			}
		}
	};
}

export interface ICompressibleAsyncDataTreeOptions<T, TFilterData = void> extends IAsyncDataTreeOptions<T, TFilterData> {
J
Joao Moreno 已提交
1109
	readonly compressionEnabled?: boolean;
1110 1111 1112
	readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider<T>;
}

J
Joao Moreno 已提交
1113 1114 1115 1116
export interface ICompressibleAsyncDataTreeOptionsUpdate extends IAsyncDataTreeOptionsUpdate {
	readonly compressionEnabled?: boolean;
}

J
Joao Moreno 已提交
1117 1118
export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {

1119
	protected readonly tree!: CompressibleObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
J
Joao Moreno 已提交
1120
	protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node));
1121
	private filter?: ITreeFilter<T, TFilterData>;
J
Joao Moreno 已提交
1122

J
Joao Moreno 已提交
1123 1124 1125
	constructor(
		user: string,
		container: HTMLElement,
J
Joao Moreno 已提交
1126 1127
		virtualDelegate: IListVirtualDelegate<T>,
		private compressionDelegate: ITreeCompressionDelegate<T>,
J
Joao Moreno 已提交
1128 1129
		renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
		dataSource: IAsyncDataSource<TInput, T>,
J
Joao Moreno 已提交
1130
		options: ICompressibleAsyncDataTreeOptions<T, TFilterData> = {}
J
Joao Moreno 已提交
1131
	) {
J
Joao Moreno 已提交
1132
		super(user, container, virtualDelegate, renderers, dataSource, options);
1133
		this.filter = options.filter;
J
Joao Moreno 已提交
1134 1135 1136 1137 1138 1139
	}

	protected createTree(
		user: string,
		container: HTMLElement,
		delegate: IListVirtualDelegate<T>,
J
Joao Moreno 已提交
1140
		renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
1141
		options: ICompressibleAsyncDataTreeOptions<T, TFilterData>
J
Joao Moreno 已提交
1142 1143
	): ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData> {
		const objectTreeDelegate = new ComposedTreeDelegate<TInput | T, IAsyncDataTreeNode<TInput, T>>(delegate);
J
Joao Moreno 已提交
1144
		const objectTreeRenderers = renderers.map(r => new CompressibleAsyncDataTreeRenderer(r, this.nodeMapper, () => this.compressibleNodeMapper, this._onDidChangeNodeSlowState.event));
1145
		const objectTreeOptions = asCompressibleObjectTreeOptions<TInput, T, TFilterData>(options) || {};
J
Joao Moreno 已提交
1146 1147 1148

		return new CompressibleObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions);
	}
J
Joao Moreno 已提交
1149 1150 1151 1152 1153 1154 1155

	protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ICompressedTreeElement<IAsyncDataTreeNode<TInput, T>> {
		return {
			incompressible: this.compressionDelegate.isIncompressible(node.element as T),
			...super.asTreeElement(node, viewStateContext)
		};
	}
1156

J
Joao Moreno 已提交
1157 1158 1159 1160
	updateOptions(options: ICompressibleAsyncDataTreeOptionsUpdate = {}): void {
		this.tree.updateOptions(options);
	}

1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187
	getViewState(): IAsyncDataTreeViewState {
		if (!this.identityProvider) {
			throw new TreeError(this.user, 'Can\'t get tree view state without an identity provider');
		}

		const getId = (element: T) => this.identityProvider!.getId(element).toString();
		const focus = this.getFocus().map(getId);
		const selection = this.getSelection().map(getId);

		const expanded: string[] = [];
		const root = this.tree.getCompressedTreeNode();
		const queue = [root];

		while (queue.length > 0) {
			const node = queue.shift()!;

			if (node !== root && node.collapsible && !node.collapsed) {
				for (const asyncNode of node.element!.elements) {
					expanded.push(getId(asyncNode.element as T));
				}
			}

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

		return { focus, selection, expanded, scrollTop: this.scrollTop };
	}
J
Joao Moreno 已提交
1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232

	protected render(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): void {
		if (!this.identityProvider) {
			return super.render(node, viewStateContext);
		}

		// Preserve traits across compressions. Hacky but does the trick.
		// This is hard to fix properly since it requires rewriting the traits
		// across trees and lists. Let's just keep it this way for now.
		const getId = (element: T) => this.identityProvider!.getId(element).toString();
		const getUncompressedIds = (nodes: IAsyncDataTreeNode<TInput, T>[]): Set<string> => {
			const result = new Set<string>();

			for (const node of nodes) {
				const compressedNode = this.tree.getCompressedTreeNode(node === this.root ? null : node);

				if (!compressedNode.element) {
					continue;
				}

				for (const node of compressedNode.element.elements) {
					result.add(getId(node.element as T));
				}
			}

			return result;
		};

		const oldSelection = getUncompressedIds(this.tree.getSelection() as IAsyncDataTreeNode<TInput, T>[]);
		const oldFocus = getUncompressedIds(this.tree.getFocus() as IAsyncDataTreeNode<TInput, T>[]);

		super.render(node, viewStateContext);

		const selection = this.getSelection();
		let didChangeSelection = false;

		const focus = this.getFocus();
		let didChangeFocus = false;

		const visit = (node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>> | null, TFilterData>) => {
			const compressedNode = node.element;

			if (compressedNode) {
				for (let i = 0; i < compressedNode.elements.length; i++) {
					const id = getId(compressedNode.elements[i].element as T);
1233
					const element = compressedNode.elements[compressedNode.elements.length - 1].element as T;
J
Joao Moreno 已提交
1234

1235 1236 1237
					// github.com/microsoft/vscode/issues/85938
					if (oldSelection.has(id) && selection.indexOf(element) === -1) {
						selection.push(element);
J
Joao Moreno 已提交
1238 1239 1240
						didChangeSelection = true;
					}

1241 1242
					if (oldFocus.has(id) && focus.indexOf(element) === -1) {
						focus.push(element);
J
Joao Moreno 已提交
1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260
						didChangeFocus = true;
					}
				}
			}

			node.children.forEach(visit);
		};

		visit(this.tree.getCompressedTreeNode(node === this.root ? null : node));

		if (didChangeSelection) {
			this.setSelection(selection);
		}

		if (didChangeFocus) {
			this.setFocus(focus);
		}
	}
1261 1262 1263 1264

	// For compressed async data trees, `TreeVisibility.Recurse` doesn't currently work
	// and we have to filter everything beforehand
	// Related to #85193 and #85835
J
João Moreno 已提交
1265
	protected processChildren(children: Iterable<T>): Iterable<T> {
1266
		if (this.filter) {
J
João Moreno 已提交
1267
			children = Iterable.filter(children, e => {
1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290
				const result = this.filter!.filter(e, TreeVisibility.Visible);
				const visibility = getVisibility(result);

				if (visibility === TreeVisibility.Recurse) {
					throw new Error('Recursive tree visibility not supported in async data compressed trees');
				}

				return visibility === TreeVisibility.Visible;
			});
		}

		return super.processChildren(children);
	}
}

function getVisibility<TFilterData>(filterResult: TreeFilterResult<TFilterData>): TreeVisibility {
	if (typeof filterResult === 'boolean') {
		return filterResult ? TreeVisibility.Visible : TreeVisibility.Hidden;
	} else if (isFilterResult(filterResult)) {
		return getVisibleState(filterResult.visibility);
	} else {
		return getVisibleState(filterResult);
	}
J
Joao Moreno 已提交
1291
}