/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { ISpliceable } from 'vs/base/common/sequence'; import { Iterator, ISequence, getSequenceIterator } from 'vs/base/common/iterator'; import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel'; import { Event } from 'vs/base/common/event'; import { ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree'; export interface IObjectTreeModelOptions extends IIndexTreeModelOptions { } export class ObjectTreeModel, TFilterData extends NonNullable = void> implements ITreeModel { private model: IndexTreeModel; private nodes = new Map>(); readonly onDidChangeCollapseState: Event>; readonly onDidChangeRenderNodeCount: Event>; get size(): number { return this.nodes.size; } constructor(list: ISpliceable>, options: IObjectTreeModelOptions = {}) { this.model = new IndexTreeModel(list, null, options); this.onDidChangeCollapseState = this.model.onDidChangeCollapseState as Event>; this.onDidChangeRenderNodeCount = this.model.onDidChangeRenderNodeCount as Event>; } setChildren( element: T | null, children: ISequence> | undefined, onDidCreateNode?: (node: ITreeNode) => void, onDidDeleteNode?: (node: ITreeNode) => void ): Iterator> { const location = this.getElementLocation(element); const insertedElements = new Set(); const _onDidCreateNode = (node: ITreeNode) => { insertedElements.add(node.element); this.nodes.set(node.element, node); if (onDidCreateNode) { onDidCreateNode(node); } }; const _onDidDeleteNode = (node: ITreeNode) => { if (!insertedElements.has(node.element)) { this.nodes.delete(node.element); } if (onDidDeleteNode) { onDidDeleteNode(node); } }; return this.model.splice( [...location, 0], Number.MAX_VALUE, this.preserveCollapseState(children), _onDidCreateNode, _onDidDeleteNode ); } private preserveCollapseState(elements: ISequence> | undefined): ISequence> { const iterator = elements ? getSequenceIterator(elements) : Iterator.empty>(); return Iterator.map(iterator, treeElement => { const node = this.nodes.get(treeElement.element); if (!node) { return treeElement; } const collapsible = typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : node.collapsible; const collapsed = typeof treeElement.collapsed !== 'undefined' ? treeElement.collapsed : (collapsible && node.collapsed); return { ...treeElement, collapsible, collapsed, children: this.preserveCollapseState(treeElement.children) }; }); } getParentElement(ref: T | null = null): T | null { const location = this.getElementLocation(ref); return this.model.getParentElement(location); } getFirstElementChild(ref: T | null = null): T | null | undefined { const location = this.getElementLocation(ref); return this.model.getFirstElementChild(location); } getLastElementAncestor(ref: T | null = null): T | null | undefined { const location = this.getElementLocation(ref); return this.model.getLastElementAncestor(location); } getListIndex(element: T): number { const location = this.getElementLocation(element); return this.model.getListIndex(location); } setCollapsed(element: T, collapsed: boolean): boolean { const location = this.getElementLocation(element); return this.model.setCollapsed(location, collapsed); } toggleCollapsed(element: T): void { const location = this.getElementLocation(element); this.model.toggleCollapsed(location); } collapseAll(): void { this.model.collapseAll(); } isCollapsible(element: T): boolean { const location = this.getElementLocation(element); return this.model.isCollapsible(location); } isCollapsed(element: T): boolean { const location = this.getElementLocation(element); return this.model.isCollapsed(location); } refilter(): void { this.model.refilter(); } getNode(element: T | null = null): ITreeNode { const location = this.getElementLocation(element); return this.model.getNode(location); } getNodeLocation(node: ITreeNode): T { return node.element; } getParentNodeLocation(element: T): T | null { const node = this.nodes.get(element); if (!node) { throw new Error(`Tree element not found: ${element}`); } return node.parent!.element; } private getElementLocation(element: T | null): number[] { if (element === null) { return []; } const node = this.nodes.get(element); if (!node) { throw new Error(`Tree element not found: ${element}`); } return this.model.getNodeLocation(node); } }