| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import { ObjectTreeModel } from './objectTreeModel.js';
- import { TreeError, WeakMapper } from './tree.js';
- import { Event } from '../../../common/event.js';
- import { Iterable } from '../../../common/iterator.js';
- function noCompress(element) {
- const elements = [element.element];
- const incompressible = element.incompressible || false;
- return {
- element: { elements, incompressible },
- children: Iterable.map(Iterable.from(element.children), noCompress),
- collapsible: element.collapsible,
- collapsed: element.collapsed
- };
- }
- // Exported only for test reasons, do not use directly
- export function compress(element) {
- const elements = [element.element];
- const incompressible = element.incompressible || false;
- let childrenIterator;
- let children;
- while (true) {
- [children, childrenIterator] = Iterable.consume(Iterable.from(element.children), 2);
- if (children.length !== 1) {
- break;
- }
- if (children[0].incompressible) {
- break;
- }
- element = children[0];
- elements.push(element.element);
- }
- return {
- element: { elements, incompressible },
- children: Iterable.map(Iterable.concat(children, childrenIterator), compress),
- collapsible: element.collapsible,
- collapsed: element.collapsed
- };
- }
- function _decompress(element, index = 0) {
- let children;
- if (index < element.element.elements.length - 1) {
- children = [_decompress(element, index + 1)];
- }
- else {
- children = Iterable.map(Iterable.from(element.children), el => _decompress(el, 0));
- }
- if (index === 0 && element.element.incompressible) {
- return {
- element: element.element.elements[index],
- children,
- incompressible: true,
- collapsible: element.collapsible,
- collapsed: element.collapsed
- };
- }
- return {
- element: element.element.elements[index],
- children,
- collapsible: element.collapsible,
- collapsed: element.collapsed
- };
- }
- // Exported only for test reasons, do not use directly
- export function decompress(element) {
- return _decompress(element, 0);
- }
- function splice(treeElement, element, children) {
- if (treeElement.element === element) {
- return Object.assign(Object.assign({}, treeElement), { children });
- }
- return Object.assign(Object.assign({}, treeElement), { children: Iterable.map(Iterable.from(treeElement.children), e => splice(e, element, children)) });
- }
- const wrapIdentityProvider = (base) => ({
- getId(node) {
- return node.elements.map(e => base.getId(e).toString()).join('\0');
- }
- });
- // Exported only for test reasons, do not use directly
- export class CompressedObjectTreeModel {
- constructor(user, list, options = {}) {
- this.user = user;
- this.rootRef = null;
- this.nodes = new Map();
- this.model = new ObjectTreeModel(user, list, options);
- this.enabled = typeof options.compressionEnabled === 'undefined' ? true : options.compressionEnabled;
- this.identityProvider = options.identityProvider;
- }
- get onDidSplice() { return this.model.onDidSplice; }
- get onDidChangeCollapseState() { return this.model.onDidChangeCollapseState; }
- get onDidChangeRenderNodeCount() { return this.model.onDidChangeRenderNodeCount; }
- setChildren(element, children = Iterable.empty(), options) {
- // Diffs must be deem, since the compression can affect nested elements.
- // @see https://github.com/microsoft/vscode/pull/114237#issuecomment-759425034
- const diffIdentityProvider = options.diffIdentityProvider && wrapIdentityProvider(options.diffIdentityProvider);
- if (element === null) {
- const compressedChildren = Iterable.map(children, this.enabled ? compress : noCompress);
- this._setChildren(null, compressedChildren, { diffIdentityProvider, diffDepth: Infinity });
- return;
- }
- const compressedNode = this.nodes.get(element);
- if (!compressedNode) {
- throw new Error('Unknown compressed tree node');
- }
- const node = this.model.getNode(compressedNode);
- const compressedParentNode = this.model.getParentNodeLocation(compressedNode);
- const parent = this.model.getNode(compressedParentNode);
- const decompressedElement = decompress(node);
- const splicedElement = splice(decompressedElement, element, children);
- const recompressedElement = (this.enabled ? compress : noCompress)(splicedElement);
- const parentChildren = parent.children
- .map(child => child === node ? recompressedElement : child);
- this._setChildren(parent.element, parentChildren, {
- diffIdentityProvider,
- diffDepth: node.depth - parent.depth,
- });
- }
- setCompressionEnabled(enabled) {
- if (enabled === this.enabled) {
- return;
- }
- this.enabled = enabled;
- const root = this.model.getNode();
- const rootChildren = root.children;
- const decompressedRootChildren = Iterable.map(rootChildren, decompress);
- const recompressedRootChildren = Iterable.map(decompressedRootChildren, enabled ? compress : noCompress);
- // it should be safe to always use deep diff mode here if an identity
- // provider is available, since we know the raw nodes are unchanged.
- this._setChildren(null, recompressedRootChildren, {
- diffIdentityProvider: this.identityProvider,
- diffDepth: Infinity,
- });
- }
- _setChildren(node, children, options) {
- const insertedElements = new Set();
- const onDidCreateNode = (node) => {
- for (const element of node.element.elements) {
- insertedElements.add(element);
- this.nodes.set(element, node.element);
- }
- };
- const onDidDeleteNode = (node) => {
- for (const element of node.element.elements) {
- if (!insertedElements.has(element)) {
- this.nodes.delete(element);
- }
- }
- };
- this.model.setChildren(node, children, Object.assign(Object.assign({}, options), { onDidCreateNode, onDidDeleteNode }));
- }
- has(element) {
- return this.nodes.has(element);
- }
- getListIndex(location) {
- const node = this.getCompressedNode(location);
- return this.model.getListIndex(node);
- }
- getListRenderCount(location) {
- const node = this.getCompressedNode(location);
- return this.model.getListRenderCount(node);
- }
- getNode(location) {
- if (typeof location === 'undefined') {
- return this.model.getNode();
- }
- const node = this.getCompressedNode(location);
- return this.model.getNode(node);
- }
- // TODO: review this
- getNodeLocation(node) {
- const compressedNode = this.model.getNodeLocation(node);
- if (compressedNode === null) {
- return null;
- }
- return compressedNode.elements[compressedNode.elements.length - 1];
- }
- // TODO: review this
- getParentNodeLocation(location) {
- const compressedNode = this.getCompressedNode(location);
- const parentNode = this.model.getParentNodeLocation(compressedNode);
- if (parentNode === null) {
- return null;
- }
- return parentNode.elements[parentNode.elements.length - 1];
- }
- getFirstElementChild(location) {
- const compressedNode = this.getCompressedNode(location);
- return this.model.getFirstElementChild(compressedNode);
- }
- isCollapsible(location) {
- const compressedNode = this.getCompressedNode(location);
- return this.model.isCollapsible(compressedNode);
- }
- setCollapsible(location, collapsible) {
- const compressedNode = this.getCompressedNode(location);
- return this.model.setCollapsible(compressedNode, collapsible);
- }
- isCollapsed(location) {
- const compressedNode = this.getCompressedNode(location);
- return this.model.isCollapsed(compressedNode);
- }
- setCollapsed(location, collapsed, recursive) {
- const compressedNode = this.getCompressedNode(location);
- return this.model.setCollapsed(compressedNode, collapsed, recursive);
- }
- expandTo(location) {
- const compressedNode = this.getCompressedNode(location);
- this.model.expandTo(compressedNode);
- }
- rerender(location) {
- const compressedNode = this.getCompressedNode(location);
- this.model.rerender(compressedNode);
- }
- refilter() {
- this.model.refilter();
- }
- getCompressedNode(element) {
- if (element === null) {
- return null;
- }
- const node = this.nodes.get(element);
- if (!node) {
- throw new TreeError(this.user, `Tree element not found: ${element}`);
- }
- return node;
- }
- }
- export const DefaultElementMapper = elements => elements[elements.length - 1];
- class CompressedTreeNodeWrapper {
- constructor(unwrapper, node) {
- this.unwrapper = unwrapper;
- this.node = node;
- }
- get element() { return this.node.element === null ? null : this.unwrapper(this.node.element); }
- get children() { return this.node.children.map(node => new CompressedTreeNodeWrapper(this.unwrapper, 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; }
- }
- function mapList(nodeMapper, list) {
- return {
- splice(start, deleteCount, toInsert) {
- list.splice(start, deleteCount, toInsert.map(node => nodeMapper.map(node)));
- },
- updateElementHeight(index, height) {
- list.updateElementHeight(index, height);
- }
- };
- }
- function mapOptions(compressedNodeUnwrapper, options) {
- return Object.assign(Object.assign({}, options), { identityProvider: options.identityProvider && {
- getId(node) {
- return options.identityProvider.getId(compressedNodeUnwrapper(node));
- }
- }, sorter: options.sorter && {
- compare(node, otherNode) {
- return options.sorter.compare(node.elements[0], otherNode.elements[0]);
- }
- }, filter: options.filter && {
- filter(node, parentVisibility) {
- return options.filter.filter(compressedNodeUnwrapper(node), parentVisibility);
- }
- } });
- }
- export class CompressibleObjectTreeModel {
- constructor(user, list, options = {}) {
- this.rootRef = null;
- this.elementMapper = options.elementMapper || DefaultElementMapper;
- const compressedNodeUnwrapper = node => this.elementMapper(node.elements);
- this.nodeMapper = new WeakMapper(node => new CompressedTreeNodeWrapper(compressedNodeUnwrapper, node));
- this.model = new CompressedObjectTreeModel(user, mapList(this.nodeMapper, list), mapOptions(compressedNodeUnwrapper, options));
- }
- get onDidSplice() {
- return Event.map(this.model.onDidSplice, ({ insertedNodes, deletedNodes }) => ({
- insertedNodes: insertedNodes.map(node => this.nodeMapper.map(node)),
- deletedNodes: deletedNodes.map(node => this.nodeMapper.map(node)),
- }));
- }
- get onDidChangeCollapseState() {
- return Event.map(this.model.onDidChangeCollapseState, ({ node, deep }) => ({
- node: this.nodeMapper.map(node),
- deep
- }));
- }
- get onDidChangeRenderNodeCount() {
- return Event.map(this.model.onDidChangeRenderNodeCount, node => this.nodeMapper.map(node));
- }
- setChildren(element, children = Iterable.empty(), options = {}) {
- this.model.setChildren(element, children, options);
- }
- setCompressionEnabled(enabled) {
- this.model.setCompressionEnabled(enabled);
- }
- has(location) {
- return this.model.has(location);
- }
- getListIndex(location) {
- return this.model.getListIndex(location);
- }
- getListRenderCount(location) {
- return this.model.getListRenderCount(location);
- }
- getNode(location) {
- return this.nodeMapper.map(this.model.getNode(location));
- }
- getNodeLocation(node) {
- return node.element;
- }
- getParentNodeLocation(location) {
- return this.model.getParentNodeLocation(location);
- }
- getFirstElementChild(location) {
- const result = this.model.getFirstElementChild(location);
- if (result === null || typeof result === 'undefined') {
- return result;
- }
- return this.elementMapper(result.elements);
- }
- isCollapsible(location) {
- return this.model.isCollapsible(location);
- }
- setCollapsible(location, collapsed) {
- return this.model.setCollapsible(location, collapsed);
- }
- isCollapsed(location) {
- return this.model.isCollapsed(location);
- }
- setCollapsed(location, collapsed, recursive) {
- return this.model.setCollapsed(location, collapsed, recursive);
- }
- expandTo(location) {
- return this.model.expandTo(location);
- }
- rerender(location) {
- return this.model.rerender(location);
- }
- refilter() {
- return this.model.refilter();
- }
- getCompressedTreeNode(location = null) {
- return this.model.getNode(location);
- }
- }
|