/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { ElementsDragAndDropData } from '../list/listView.js'; import { ComposedTreeDelegate } from './abstractTree.js'; import { getVisibleState, isFilterResult } from './indexTreeModel.js'; import { CompressibleObjectTree, ObjectTree } from './objectTree.js'; import { TreeError, WeakMapper } from './tree.js'; import { createCancelablePromise, Promises, timeout } from '../../../common/async.js'; import { Codicon } from '../../../common/codicons.js'; import { isCancellationError, onUnexpectedError } from '../../../common/errors.js'; import { Emitter, Event } from '../../../common/event.js'; import { Iterable } from '../../../common/iterator.js'; import { DisposableStore, dispose } from '../../../common/lifecycle.js'; import { isIterable } from '../../../common/types.js'; function createAsyncDataTreeNode(props) { return Object.assign(Object.assign({}, props), { children: [], refreshPromise: undefined, stale: true, slow: false, collapsedByDefault: undefined }); } function isAncestor(ancestor, descendant) { if (!descendant.parent) { return false; } else if (descendant.parent === ancestor) { return true; } else { return isAncestor(ancestor, descendant.parent); } } function intersects(node, other) { return node === other || isAncestor(node, other) || isAncestor(other, node); } class AsyncDataTreeNodeWrapper { constructor(node) { this.node = node; } get element() { return this.node.element.element; } get children() { return this.node.children.map(node => new AsyncDataTreeNodeWrapper(node)); } get depth() { return this.node.depth; } get visibleChildrenCount() { return this.node.visibleChildrenCount; } get visibleChildIndex() { return this.node.visibleChildIndex; } get collapsible() { return this.node.collapsible; } get collapsed() { return this.node.collapsed; } get visible() { return this.node.visible; } get filterData() { return this.node.filterData; } } class AsyncDataTreeRenderer { constructor(renderer, nodeMapper, onDidChangeTwistieState) { this.renderer = renderer; this.nodeMapper = nodeMapper; this.onDidChangeTwistieState = onDidChangeTwistieState; this.renderedNodes = new Map(); this.templateId = renderer.templateId; } renderTemplate(container) { const templateData = this.renderer.renderTemplate(container); return { templateData }; } renderElement(node, index, templateData, height) { this.renderer.renderElement(this.nodeMapper.map(node), index, templateData.templateData, height); } renderTwistie(element, twistieElement) { if (element.slow) { twistieElement.classList.add(...Codicon.treeItemLoading.classNamesArray); return true; } else { twistieElement.classList.remove(...Codicon.treeItemLoading.classNamesArray); return false; } } disposeElement(node, index, templateData, height) { var _a, _b; (_b = (_a = this.renderer).disposeElement) === null || _b === void 0 ? void 0 : _b.call(_a, this.nodeMapper.map(node), index, templateData.templateData, height); } disposeTemplate(templateData) { this.renderer.disposeTemplate(templateData.templateData); } dispose() { this.renderedNodes.clear(); } } function asTreeEvent(e) { return { browserEvent: e.browserEvent, elements: e.elements.map(e => e.element) }; } function asTreeMouseEvent(e) { return { browserEvent: e.browserEvent, element: e.element && e.element.element, target: e.target }; } class AsyncDataTreeElementsDragAndDropData extends ElementsDragAndDropData { constructor(data) { super(data.elements.map(node => node.element)); this.data = data; } } function asAsyncDataTreeDragAndDropData(data) { if (data instanceof ElementsDragAndDropData) { return new AsyncDataTreeElementsDragAndDropData(data); } return data; } class AsyncDataTreeNodeListDragAndDrop { constructor(dnd) { this.dnd = dnd; } getDragURI(node) { return this.dnd.getDragURI(node.element); } getDragLabel(nodes, originalEvent) { if (this.dnd.getDragLabel) { return this.dnd.getDragLabel(nodes.map(node => node.element), originalEvent); } return undefined; } onDragStart(data, originalEvent) { var _a, _b; (_b = (_a = this.dnd).onDragStart) === null || _b === void 0 ? void 0 : _b.call(_a, asAsyncDataTreeDragAndDropData(data), originalEvent); } onDragOver(data, targetNode, targetIndex, originalEvent, raw = true) { return this.dnd.onDragOver(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent); } drop(data, targetNode, targetIndex, originalEvent) { this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent); } onDragEnd(originalEvent) { var _a, _b; (_b = (_a = this.dnd).onDragEnd) === null || _b === void 0 ? void 0 : _b.call(_a, originalEvent); } } function asObjectTreeOptions(options) { return options && Object.assign(Object.assign({}, options), { collapseByDefault: true, identityProvider: options.identityProvider && { getId(el) { return options.identityProvider.getId(el.element); } }, dnd: options.dnd && new AsyncDataTreeNodeListDragAndDrop(options.dnd), multipleSelectionController: options.multipleSelectionController && { isSelectionSingleChangeEvent(e) { return options.multipleSelectionController.isSelectionSingleChangeEvent(Object.assign(Object.assign({}, e), { element: e.element })); }, isSelectionRangeChangeEvent(e) { return options.multipleSelectionController.isSelectionRangeChangeEvent(Object.assign(Object.assign({}, e), { element: e.element })); } }, accessibilityProvider: options.accessibilityProvider && Object.assign(Object.assign({}, options.accessibilityProvider), { getPosInSet: undefined, getSetSize: undefined, getRole: options.accessibilityProvider.getRole ? (el) => { return options.accessibilityProvider.getRole(el.element); } : () => 'treeitem', isChecked: options.accessibilityProvider.isChecked ? (e) => { var _a; return !!((_a = options.accessibilityProvider) === null || _a === void 0 ? void 0 : _a.isChecked(e.element)); } : undefined, getAriaLabel(e) { return options.accessibilityProvider.getAriaLabel(e.element); }, getWidgetAriaLabel() { return options.accessibilityProvider.getWidgetAriaLabel(); }, getWidgetRole: options.accessibilityProvider.getWidgetRole ? () => options.accessibilityProvider.getWidgetRole() : () => 'tree', getAriaLevel: options.accessibilityProvider.getAriaLevel && (node => { return options.accessibilityProvider.getAriaLevel(node.element); }), getActiveDescendantId: options.accessibilityProvider.getActiveDescendantId && (node => { return options.accessibilityProvider.getActiveDescendantId(node.element); }) }), filter: options.filter && { filter(e, parentVisibility) { return options.filter.filter(e.element, parentVisibility); } }, keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && Object.assign(Object.assign({}, options.keyboardNavigationLabelProvider), { getKeyboardNavigationLabel(e) { return options.keyboardNavigationLabelProvider.getKeyboardNavigationLabel(e.element); } }), sorter: undefined, expandOnlyOnTwistieClick: typeof options.expandOnlyOnTwistieClick === 'undefined' ? undefined : (typeof options.expandOnlyOnTwistieClick !== 'function' ? options.expandOnlyOnTwistieClick : (e => options.expandOnlyOnTwistieClick(e.element))), additionalScrollHeight: options.additionalScrollHeight }); } function dfs(node, fn) { fn(node); node.children.forEach(child => dfs(child, fn)); } export class AsyncDataTree { constructor(user, container, delegate, renderers, dataSource, options = {}) { this.user = user; this.dataSource = dataSource; this.nodes = new Map(); this.subTreeRefreshPromises = new Map(); this.refreshPromises = new Map(); this._onDidRender = new Emitter(); this._onDidChangeNodeSlowState = new Emitter(); this.nodeMapper = new WeakMapper(node => new AsyncDataTreeNodeWrapper(node)); this.disposables = new DisposableStore(); this.identityProvider = options.identityProvider; this.autoExpandSingleChildren = typeof options.autoExpandSingleChildren === 'undefined' ? false : options.autoExpandSingleChildren; this.sorter = options.sorter; this.collapseByDefault = options.collapseByDefault; this.tree = this.createTree(user, container, delegate, renderers, options); this.onDidChangeFindMode = this.tree.onDidChangeFindMode; this.root = createAsyncDataTreeNode({ element: undefined, parent: null, hasChildren: true }); if (this.identityProvider) { this.root = Object.assign(Object.assign({}, this.root), { id: null }); } this.nodes.set(null, this.root); this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables); } get onDidChangeFocus() { return Event.map(this.tree.onDidChangeFocus, asTreeEvent); } get onDidChangeSelection() { return Event.map(this.tree.onDidChangeSelection, asTreeEvent); } get onMouseDblClick() { return Event.map(this.tree.onMouseDblClick, asTreeMouseEvent); } get onPointer() { return Event.map(this.tree.onPointer, asTreeMouseEvent); } get onDidFocus() { return this.tree.onDidFocus; } get onDidChangeModel() { return this.tree.onDidChangeModel; } get onDidChangeCollapseState() { return this.tree.onDidChangeCollapseState; } get onDidChangeFindOpenState() { return this.tree.onDidChangeFindOpenState; } get onDidDispose() { return this.tree.onDidDispose; } createTree(user, container, delegate, renderers, options) { const objectTreeDelegate = new ComposedTreeDelegate(delegate); const objectTreeRenderers = renderers.map(r => new AsyncDataTreeRenderer(r, this.nodeMapper, this._onDidChangeNodeSlowState.event)); const objectTreeOptions = asObjectTreeOptions(options) || {}; return new ObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions); } updateOptions(options = {}) { this.tree.updateOptions(options); } // Widget getHTMLElement() { return this.tree.getHTMLElement(); } get scrollTop() { return this.tree.scrollTop; } set scrollTop(scrollTop) { this.tree.scrollTop = scrollTop; } domFocus() { this.tree.domFocus(); } layout(height, width) { this.tree.layout(height, width); } style(styles) { this.tree.style(styles); } // Model getInput() { return this.root.element; } setInput(input, viewState) { return __awaiter(this, void 0, void 0, function* () { this.refreshPromises.forEach(promise => promise.cancel()); this.refreshPromises.clear(); this.root.element = input; const viewStateContext = viewState && { viewState, focus: [], selection: [] }; yield this._updateChildren(input, true, false, viewStateContext); if (viewStateContext) { this.tree.setFocus(viewStateContext.focus); this.tree.setSelection(viewStateContext.selection); } if (viewState && typeof viewState.scrollTop === 'number') { this.scrollTop = viewState.scrollTop; } }); } _updateChildren(element = this.root.element, recursive = true, rerender = false, viewStateContext, options) { return __awaiter(this, void 0, void 0, function* () { if (typeof this.root.element === 'undefined') { throw new TreeError(this.user, 'Tree input not set'); } if (this.root.refreshPromise) { yield this.root.refreshPromise; yield Event.toPromise(this._onDidRender.event); } const node = this.getDataNode(element); yield this.refreshAndRenderNode(node, recursive, viewStateContext, options); if (rerender) { try { this.tree.rerender(node); } catch (_a) { // missing nodes are fine, this could've resulted from // parallel refresh calls, removing `node` altogether } } }); } // View rerender(element) { if (element === undefined || element === this.root.element) { this.tree.rerender(); return; } const node = this.getDataNode(element); this.tree.rerender(node); } // Tree getNode(element = this.root.element) { const dataNode = this.getDataNode(element); const node = this.tree.getNode(dataNode === this.root ? null : dataNode); return this.nodeMapper.map(node); } collapse(element, recursive = false) { const node = this.getDataNode(element); return this.tree.collapse(node === this.root ? null : node, recursive); } expand(element, recursive = false) { return __awaiter(this, void 0, void 0, function* () { if (typeof this.root.element === 'undefined') { throw new TreeError(this.user, 'Tree input not set'); } if (this.root.refreshPromise) { yield this.root.refreshPromise; yield Event.toPromise(this._onDidRender.event); } const node = this.getDataNode(element); if (this.tree.hasElement(node) && !this.tree.isCollapsible(node)) { return false; } if (node.refreshPromise) { yield this.root.refreshPromise; yield Event.toPromise(this._onDidRender.event); } if (node !== this.root && !node.refreshPromise && !this.tree.isCollapsed(node)) { return false; } const result = this.tree.expand(node === this.root ? null : node, recursive); if (node.refreshPromise) { yield this.root.refreshPromise; yield Event.toPromise(this._onDidRender.event); } return result; }); } setSelection(elements, browserEvent) { const nodes = elements.map(e => this.getDataNode(e)); this.tree.setSelection(nodes, browserEvent); } getSelection() { const nodes = this.tree.getSelection(); return nodes.map(n => n.element); } setFocus(elements, browserEvent) { const nodes = elements.map(e => this.getDataNode(e)); this.tree.setFocus(nodes, browserEvent); } getFocus() { const nodes = this.tree.getFocus(); return nodes.map(n => n.element); } reveal(element, relativeTop) { this.tree.reveal(this.getDataNode(element), relativeTop); } // Tree navigation getParentElement(element) { const node = this.tree.getParentElement(this.getDataNode(element)); return (node && node.element); } getFirstElementChild(element = this.root.element) { const dataNode = this.getDataNode(element); const node = this.tree.getFirstElementChild(dataNode === this.root ? null : dataNode); return (node && node.element); } // Implementation getDataNode(element) { const node = this.nodes.get((element === this.root.element ? null : element)); if (!node) { throw new TreeError(this.user, `Data tree node not found: ${element}`); } return node; } refreshAndRenderNode(node, recursive, viewStateContext, options) { return __awaiter(this, void 0, void 0, function* () { yield this.refreshNode(node, recursive, viewStateContext); this.render(node, viewStateContext, options); }); } refreshNode(node, recursive, viewStateContext) { return __awaiter(this, void 0, void 0, function* () { let result; this.subTreeRefreshPromises.forEach((refreshPromise, refreshNode) => { if (!result && intersects(refreshNode, node)) { result = refreshPromise.then(() => this.refreshNode(node, recursive, viewStateContext)); } }); if (result) { return result; } if (node !== this.root) { const treeNode = this.tree.getNode(node); if (treeNode.collapsed) { node.hasChildren = !!this.dataSource.hasChildren(node.element); node.stale = true; return; } } return this.doRefreshSubTree(node, recursive, viewStateContext); }); } doRefreshSubTree(node, recursive, viewStateContext) { return __awaiter(this, void 0, void 0, function* () { let done; node.refreshPromise = new Promise(c => done = c); this.subTreeRefreshPromises.set(node, node.refreshPromise); node.refreshPromise.finally(() => { node.refreshPromise = undefined; this.subTreeRefreshPromises.delete(node); }); try { const childrenToRefresh = yield this.doRefreshNode(node, recursive, viewStateContext); node.stale = false; yield Promises.settled(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext))); } finally { done(); } }); } doRefreshNode(node, recursive, viewStateContext) { return __awaiter(this, void 0, void 0, function* () { node.hasChildren = !!this.dataSource.hasChildren(node.element); let childrenPromise; if (!node.hasChildren) { childrenPromise = Promise.resolve(Iterable.empty()); } else { const children = this.doGetChildren(node); if (isIterable(children)) { childrenPromise = Promise.resolve(children); } else { const slowTimeout = timeout(800); slowTimeout.then(() => { node.slow = true; this._onDidChangeNodeSlowState.fire(node); }, _ => null); childrenPromise = children.finally(() => slowTimeout.cancel()); } } try { const children = yield childrenPromise; return this.setChildren(node, children, recursive, viewStateContext); } catch (err) { if (node !== this.root && this.tree.hasElement(node)) { this.tree.collapse(node); } if (isCancellationError(err)) { return []; } throw err; } finally { if (node.slow) { node.slow = false; this._onDidChangeNodeSlowState.fire(node); } } }); } doGetChildren(node) { let result = this.refreshPromises.get(node); if (result) { return result; } const children = this.dataSource.getChildren(node.element); if (isIterable(children)) { return this.processChildren(children); } else { result = createCancelablePromise(() => __awaiter(this, void 0, void 0, function* () { return this.processChildren(yield children); })); this.refreshPromises.set(node, result); return result.finally(() => { this.refreshPromises.delete(node); }); } } _onDidChangeCollapseState({ node, deep }) { if (node.element === null) { return; } if (!node.collapsed && node.element.stale) { if (deep) { this.collapse(node.element.element); } else { this.refreshAndRenderNode(node.element, false) .catch(onUnexpectedError); } } } setChildren(node, childrenElementsIterable, recursive, viewStateContext) { const childrenElements = [...childrenElementsIterable]; // perf: if the node was and still is a leaf, avoid all this hassle if (node.children.length === 0 && childrenElements.length === 0) { return []; } const nodesToForget = new Map(); const childrenTreeNodesById = new Map(); for (const child of node.children) { nodesToForget.set(child.element, child); if (this.identityProvider) { const collapsed = this.tree.isCollapsed(child); childrenTreeNodesById.set(child.id, { node: child, collapsed }); } } const childrenToRefresh = []; const children = childrenElements.map(element => { const hasChildren = !!this.dataSource.hasChildren(element); if (!this.identityProvider) { const asyncDataTreeNode = createAsyncDataTreeNode({ element, parent: node, hasChildren }); if (hasChildren && this.collapseByDefault && !this.collapseByDefault(element)) { asyncDataTreeNode.collapsedByDefault = false; childrenToRefresh.push(asyncDataTreeNode); } return asyncDataTreeNode; } const id = this.identityProvider.getId(element).toString(); const result = childrenTreeNodesById.get(id); if (result) { const asyncDataTreeNode = result.node; nodesToForget.delete(asyncDataTreeNode.element); this.nodes.delete(asyncDataTreeNode.element); this.nodes.set(element, asyncDataTreeNode); asyncDataTreeNode.element = element; asyncDataTreeNode.hasChildren = hasChildren; if (recursive) { if (result.collapsed) { asyncDataTreeNode.children.forEach(node => dfs(node, node => this.nodes.delete(node.element))); asyncDataTreeNode.children.splice(0, asyncDataTreeNode.children.length); asyncDataTreeNode.stale = true; } else { childrenToRefresh.push(asyncDataTreeNode); } } else if (hasChildren && this.collapseByDefault && !this.collapseByDefault(element)) { asyncDataTreeNode.collapsedByDefault = false; childrenToRefresh.push(asyncDataTreeNode); } return asyncDataTreeNode; } const childAsyncDataTreeNode = createAsyncDataTreeNode({ element, parent: node, id, hasChildren }); if (viewStateContext && viewStateContext.viewState.focus && viewStateContext.viewState.focus.indexOf(id) > -1) { viewStateContext.focus.push(childAsyncDataTreeNode); } if (viewStateContext && viewStateContext.viewState.selection && viewStateContext.viewState.selection.indexOf(id) > -1) { viewStateContext.selection.push(childAsyncDataTreeNode); } if (viewStateContext && viewStateContext.viewState.expanded && viewStateContext.viewState.expanded.indexOf(id) > -1) { childrenToRefresh.push(childAsyncDataTreeNode); } else if (hasChildren && this.collapseByDefault && !this.collapseByDefault(element)) { childAsyncDataTreeNode.collapsedByDefault = false; childrenToRefresh.push(childAsyncDataTreeNode); } return childAsyncDataTreeNode; }); for (const node of nodesToForget.values()) { dfs(node, node => this.nodes.delete(node.element)); } for (const child of children) { this.nodes.set(child.element, child); } node.children.splice(0, node.children.length, ...children); // 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]); } return childrenToRefresh; } render(node, viewStateContext, options) { const children = node.children.map(node => this.asTreeElement(node, viewStateContext)); const objectTreeOptions = options && Object.assign(Object.assign({}, options), { diffIdentityProvider: options.diffIdentityProvider && { getId(node) { return options.diffIdentityProvider.getId(node.element); } } }); this.tree.setChildren(node === this.root ? null : node, children, objectTreeOptions); if (node !== this.root) { this.tree.setCollapsible(node, node.hasChildren); } this._onDidRender.fire(); } asTreeElement(node, viewStateContext) { if (node.stale) { return { element: node, collapsible: node.hasChildren, collapsed: true }; } let collapsed; 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, children: node.hasChildren ? Iterable.map(node.children, child => this.asTreeElement(child, viewStateContext)) : [], collapsible: node.hasChildren, collapsed }; } processChildren(children) { if (this.sorter) { children = [...children].sort(this.sorter.compare.bind(this.sorter)); } return children; } dispose() { this.disposables.dispose(); } } class CompressibleAsyncDataTreeNodeWrapper { constructor(node) { this.node = node; } get element() { return { elements: this.node.element.elements.map(e => e.element), incompressible: this.node.element.incompressible }; } get children() { return this.node.children.map(node => new CompressibleAsyncDataTreeNodeWrapper(node)); } get depth() { return this.node.depth; } get visibleChildrenCount() { return this.node.visibleChildrenCount; } get visibleChildIndex() { return this.node.visibleChildIndex; } get collapsible() { return this.node.collapsible; } get collapsed() { return this.node.collapsed; } get visible() { return this.node.visible; } get filterData() { return this.node.filterData; } } class CompressibleAsyncDataTreeRenderer { constructor(renderer, nodeMapper, compressibleNodeMapperProvider, onDidChangeTwistieState) { this.renderer = renderer; this.nodeMapper = nodeMapper; this.compressibleNodeMapperProvider = compressibleNodeMapperProvider; this.onDidChangeTwistieState = onDidChangeTwistieState; this.renderedNodes = new Map(); this.disposables = []; this.templateId = renderer.templateId; } renderTemplate(container) { const templateData = this.renderer.renderTemplate(container); return { templateData }; } renderElement(node, index, templateData, height) { this.renderer.renderElement(this.nodeMapper.map(node), index, templateData.templateData, height); } renderCompressedElements(node, index, templateData, height) { this.renderer.renderCompressedElements(this.compressibleNodeMapperProvider().map(node), index, templateData.templateData, height); } renderTwistie(element, twistieElement) { if (element.slow) { twistieElement.classList.add(...Codicon.treeItemLoading.classNamesArray); return true; } else { twistieElement.classList.remove(...Codicon.treeItemLoading.classNamesArray); return false; } } disposeElement(node, index, templateData, height) { var _a, _b; (_b = (_a = this.renderer).disposeElement) === null || _b === void 0 ? void 0 : _b.call(_a, this.nodeMapper.map(node), index, templateData.templateData, height); } disposeCompressedElements(node, index, templateData, height) { var _a, _b; (_b = (_a = this.renderer).disposeCompressedElements) === null || _b === void 0 ? void 0 : _b.call(_a, this.compressibleNodeMapperProvider().map(node), index, templateData.templateData, height); } disposeTemplate(templateData) { this.renderer.disposeTemplate(templateData.templateData); } dispose() { this.renderedNodes.clear(); this.disposables = dispose(this.disposables); } } function asCompressibleObjectTreeOptions(options) { const objectTreeOptions = options && asObjectTreeOptions(options); return objectTreeOptions && Object.assign(Object.assign({}, objectTreeOptions), { keyboardNavigationLabelProvider: objectTreeOptions.keyboardNavigationLabelProvider && Object.assign(Object.assign({}, objectTreeOptions.keyboardNavigationLabelProvider), { getCompressedNodeKeyboardNavigationLabel(els) { return options.keyboardNavigationLabelProvider.getCompressedNodeKeyboardNavigationLabel(els.map(e => e.element)); } }) }); } export class CompressibleAsyncDataTree extends AsyncDataTree { constructor(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, options = {}) { super(user, container, virtualDelegate, renderers, dataSource, options); this.compressionDelegate = compressionDelegate; this.compressibleNodeMapper = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node)); this.filter = options.filter; } createTree(user, container, delegate, renderers, options) { const objectTreeDelegate = new ComposedTreeDelegate(delegate); const objectTreeRenderers = renderers.map(r => new CompressibleAsyncDataTreeRenderer(r, this.nodeMapper, () => this.compressibleNodeMapper, this._onDidChangeNodeSlowState.event)); const objectTreeOptions = asCompressibleObjectTreeOptions(options) || {}; return new CompressibleObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions); } asTreeElement(node, viewStateContext) { return Object.assign({ incompressible: this.compressionDelegate.isIncompressible(node.element) }, super.asTreeElement(node, viewStateContext)); } updateOptions(options = {}) { this.tree.updateOptions(options); } render(node, viewStateContext) { 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) => this.identityProvider.getId(element).toString(); const getUncompressedIds = (nodes) => { const result = new Set(); 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)); } } return result; }; const oldSelection = getUncompressedIds(this.tree.getSelection()); const oldFocus = getUncompressedIds(this.tree.getFocus()); super.render(node, viewStateContext); const selection = this.getSelection(); let didChangeSelection = false; const focus = this.getFocus(); let didChangeFocus = false; const visit = (node) => { const compressedNode = node.element; if (compressedNode) { for (let i = 0; i < compressedNode.elements.length; i++) { const id = getId(compressedNode.elements[i].element); const element = compressedNode.elements[compressedNode.elements.length - 1].element; // github.com/microsoft/vscode/issues/85938 if (oldSelection.has(id) && selection.indexOf(element) === -1) { selection.push(element); didChangeSelection = true; } if (oldFocus.has(id) && focus.indexOf(element) === -1) { focus.push(element); 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); } } // For compressed async data trees, `TreeVisibility.Recurse` doesn't currently work // and we have to filter everything beforehand // Related to #85193 and #85835 processChildren(children) { if (this.filter) { children = Iterable.filter(children, e => { const result = this.filter.filter(e, 1 /* TreeVisibility.Visible */); const visibility = getVisibility(result); if (visibility === 2 /* TreeVisibility.Recurse */) { throw new Error('Recursive tree visibility not supported in async data compressed trees'); } return visibility === 1 /* TreeVisibility.Visible */; }); } return super.processChildren(children); } } function getVisibility(filterResult) { if (typeof filterResult === 'boolean') { return filterResult ? 1 /* TreeVisibility.Visible */ : 0 /* TreeVisibility.Hidden */; } else if (isFilterResult(filterResult)) { return getVisibleState(filterResult.visibility); } else { return getVisibleState(filterResult); } }