UNPKG

monaco-editor

Version:
846 lines (845 loc) • 36.1 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { ElementsDragAndDropData } from '../list/listView.js'; import { ComposedTreeDelegate } from './abstractTree.js'; import { getVisibleState, isFilterResult } from './indexTreeModel.js'; import { CompressibleObjectTree, ObjectTree } from './objectTree.js'; import { ObjectTreeElementCollapseState, TreeError, WeakMapper } from './tree.js'; import { createCancelablePromise, Promises, timeout } from '../../../common/async.js'; import { Codicon } from '../../../common/codicons.js'; import { ThemeIcon } from '../../../common/themables.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 { ...props, children: [], refreshPromise: undefined, stale: true, slow: false, forceExpanded: false }; } 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 { 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; } constructor(node) { this.node = node; } } 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(...ThemeIcon.asClassNameArray(Codicon.treeItemLoading)); return true; } else { twistieElement.classList.remove(...ThemeIcon.asClassNameArray(Codicon.treeItemLoading)); return false; } } disposeElement(node, index, templateData, height) { this.renderer.disposeElement?.(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) { this.dnd.onDragStart?.(asAsyncDataTreeDragAndDropData(data), originalEvent); } onDragOver(data, targetNode, targetIndex, targetSector, originalEvent, raw = true) { return this.dnd.onDragOver(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, targetSector, originalEvent); } drop(data, targetNode, targetIndex, targetSector, originalEvent) { this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, targetSector, originalEvent); } onDragEnd(originalEvent) { this.dnd.onDragEnd?.(originalEvent); } dispose() { this.dnd.dispose(); } } function asObjectTreeOptions(options) { return options && { ...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({ ...e, element: e.element }); }, isSelectionRangeChangeEvent(e) { return options.multipleSelectionController.isSelectionRangeChangeEvent({ ...e, element: e.element }); } }, accessibilityProvider: options.accessibilityProvider && { ...options.accessibilityProvider, getPosInSet: undefined, getSetSize: undefined, getRole: options.accessibilityProvider.getRole ? (el) => { return options.accessibilityProvider.getRole(el.element); } : () => 'treeitem', isChecked: options.accessibilityProvider.isChecked ? (e) => { return !!(options.accessibilityProvider?.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 && { ...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))), defaultFindVisibility: e => { if (e.hasChildren && e.stale) { return 1 /* TreeVisibility.Visible */; } else if (typeof options.defaultFindVisibility === 'number') { return options.defaultFindVisibility; } else if (typeof options.defaultFindVisibility === 'undefined') { return 2 /* TreeVisibility.Recurse */; } else { return options.defaultFindVisibility(e.element); } } }; } function dfs(node, fn) { fn(node); node.children.forEach(child => dfs(child, fn)); } export class AsyncDataTree { get onDidScroll() { return this.tree.onDidScroll; } 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; } /** * To be used internally only! * @deprecated */ get onDidChangeModel() { return this.tree.onDidChangeModel; } get onDidChangeCollapseState() { return this.tree.onDidChangeCollapseState; } get onDidChangeFindOpenState() { return this.tree.onDidChangeFindOpenState; } get onDidChangeStickyScrollFocused() { return this.tree.onDidChangeStickyScrollFocused; } get onDidDispose() { return this.tree.onDidDispose; } 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.getDefaultCollapseState = e => options.collapseByDefault ? (options.collapseByDefault(e) ? ObjectTreeElementCollapseState.PreserveOrCollapsed : ObjectTreeElementCollapseState.PreserveOrExpanded) : undefined; this.tree = this.createTree(user, container, delegate, renderers, options); this.onDidChangeFindMode = this.tree.onDidChangeFindMode; this.onDidChangeFindMatchType = this.tree.onDidChangeFindMatchType; this.root = createAsyncDataTreeNode({ element: undefined, parent: null, hasChildren: true, defaultCollapseState: undefined }); if (this.identityProvider) { this.root = { ...this.root, id: null }; } this.nodes.set(null, this.root); this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables); } 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; } get scrollHeight() { return this.tree.scrollHeight; } get renderHeight() { return this.tree.renderHeight; } domFocus() { this.tree.domFocus(); } layout(height, width) { this.tree.layout(height, width); } style(styles) { this.tree.style(styles); } // Model getInput() { return this.root.element; } async setInput(input, viewState) { this.refreshPromises.forEach(promise => promise.cancel()); this.refreshPromises.clear(); this.root.element = input; const viewStateContext = viewState && { viewState, focus: [], selection: [] }; await 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; } } async _updateChildren(element = this.root.element, recursive = true, rerender = false, viewStateContext, options) { if (typeof this.root.element === 'undefined') { throw new TreeError(this.user, 'Tree input not set'); } if (this.root.refreshPromise) { await this.root.refreshPromise; await Event.toPromise(this._onDidRender.event); } const node = this.getDataNode(element); await this.refreshAndRenderNode(node, recursive, viewStateContext, options); if (rerender) { try { this.tree.rerender(node); } catch { // 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); } async expand(element, recursive = false) { if (typeof this.root.element === 'undefined') { throw new TreeError(this.user, 'Tree input not set'); } if (this.root.refreshPromise) { await this.root.refreshPromise; await Event.toPromise(this._onDidRender.event); } const node = this.getDataNode(element); if (this.tree.hasElement(node) && !this.tree.isCollapsible(node)) { return false; } if (node.refreshPromise) { await this.root.refreshPromise; await 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) { await this.root.refreshPromise; await 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; } async refreshAndRenderNode(node, recursive, viewStateContext, options) { await this.refreshNode(node, recursive, viewStateContext); if (this.disposables.isDisposed) { return; // tree disposed during refresh (#199264) } this.render(node, viewStateContext, options); } async refreshNode(node, recursive, viewStateContext) { 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; this.setChildren(node, [], recursive, viewStateContext); return; } } return this.doRefreshSubTree(node, recursive, viewStateContext); } async doRefreshSubTree(node, recursive, viewStateContext) { 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 = await this.doRefreshNode(node, recursive, viewStateContext); node.stale = false; await Promises.settled(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext))); } finally { done(); } } async doRefreshNode(node, recursive, viewStateContext) { 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 = await 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(async () => this.processChildren(await 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) { childrenTreeNodesById.set(child.id, { node: child, collapsed: this.tree.hasElement(child) && this.tree.isCollapsed(child) }); } } const childrenToRefresh = []; const children = childrenElements.map(element => { const hasChildren = !!this.dataSource.hasChildren(element); if (!this.identityProvider) { const asyncDataTreeNode = createAsyncDataTreeNode({ element, parent: node, hasChildren, defaultCollapseState: this.getDefaultCollapseState(element) }); if (hasChildren && asyncDataTreeNode.defaultCollapseState === ObjectTreeElementCollapseState.PreserveOrExpanded) { 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 && !result.collapsed) { childrenToRefresh.push(asyncDataTreeNode); } return asyncDataTreeNode; } const childAsyncDataTreeNode = createAsyncDataTreeNode({ element, parent: node, id, hasChildren, defaultCollapseState: this.getDefaultCollapseState(element) }); 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 && childAsyncDataTreeNode.defaultCollapseState === ObjectTreeElementCollapseState.PreserveOrExpanded) { 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].forceExpanded = true; childrenToRefresh.push(children[0]); } return childrenToRefresh; } render(node, viewStateContext, options) { const children = node.children.map(node => this.asTreeElement(node, viewStateContext)); const objectTreeOptions = options && { ...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 if (node.forceExpanded) { collapsed = false; node.forceExpanded = false; } else { collapsed = node.defaultCollapseState; } 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(); this.tree.dispose(); } } class CompressibleAsyncDataTreeNodeWrapper { 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; } constructor(node) { this.node = node; } } 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(...ThemeIcon.asClassNameArray(Codicon.treeItemLoading)); return true; } else { twistieElement.classList.remove(...ThemeIcon.asClassNameArray(Codicon.treeItemLoading)); return false; } } disposeElement(node, index, templateData, height) { this.renderer.disposeElement?.(this.nodeMapper.map(node), index, templateData.templateData, height); } disposeCompressedElements(node, index, templateData, height) { this.renderer.disposeCompressedElements?.(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 && { ...objectTreeOptions, keyboardNavigationLabelProvider: objectTreeOptions.keyboardNavigationLabelProvider && { ...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 { incompressible: this.compressionDelegate.isIncompressible(node.element), ...super.asTreeElement(node, viewStateContext) }; } updateOptions(options = {}) { this.tree.updateOptions(options); } render(node, viewStateContext, options) { 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, options); 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); } }