diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 02e4983c815c248023ecd015a3d96be9d7719b38..eed797a7705c8ed6e5f469157d422397d5234d49 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./tree'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IListOptions, List, IIdentityProvider, IMultipleSelectionController } from 'vs/base/browser/ui/list/listWidget'; import { TreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/treeModel'; import { IIterator, empty } from 'vs/base/common/iterator'; import { IDelegate, IRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list'; import { append, $ } from 'vs/base/browser/dom'; +import { Event, Relay } from 'vs/base/common/event'; function toTreeListOptions(options?: IListOptions): IListOptions> { if (!options) { @@ -56,15 +57,22 @@ class TreeDelegate implements IDelegate> { interface ITreeListTemplateData { twistie: HTMLElement; + elementDisposable: IDisposable; templateData: T; } class TreeRenderer implements IRenderer, ITreeListTemplateData> { readonly templateId: string; + private renderedNodes = new Map, ITreeListTemplateData>(); + private disposables: IDisposable[] = []; - constructor(private renderer: IRenderer) { + constructor( + private renderer: IRenderer, + onDidChangeCollapseState: Event> + ) { this.templateId = renderer.templateId; + onDidChangeCollapseState(this.onDidChangeCollapseState, this, this.disposables); } renderTemplate(container: HTMLElement): ITreeListTemplateData { @@ -73,20 +81,39 @@ class TreeRenderer implements IRenderer, ITreeLis const contents = append(el, $('.tl-contents')); const templateData = this.renderer.renderTemplate(contents); - return { twistie, templateData }; + return { twistie, elementDisposable: EmptyDisposable, templateData }; } - renderElement(element: ITreeNode, index: number, templateData: ITreeListTemplateData): void { - const { twistie } = templateData; - twistie.innerText = element.children.length === 0 ? '' : (element.collapsed ? '▹' : '◢'); - twistie.style.width = `${10 + element.depth * 10}px`; + renderElement(node: ITreeNode, index: number, templateData: ITreeListTemplateData): void { + templateData.elementDisposable.dispose(); + + this.renderedNodes.set(node, templateData); + templateData.elementDisposable = toDisposable(() => this.renderedNodes.delete(node)); - this.renderer.renderElement(element.element, index, templateData.templateData); + templateData.twistie.innerText = node.children.length === 0 ? '' : (node.collapsed ? '▹' : '◢'); + templateData.twistie.style.width = `${10 + node.depth * 10}px`; + + this.renderer.renderElement(node.element, index, templateData.templateData); } disposeTemplate(templateData: ITreeListTemplateData): void { this.renderer.disposeTemplate(templateData.templateData); } + + private onDidChangeCollapseState(node: ITreeNode): void { + const templateData = this.renderedNodes.get(node); + + if (!templateData) { + return; + } + + templateData.twistie.innerText = node.children.length === 0 ? '' : (node.collapsed ? '▹' : '◢'); + } + + dispose(): void { + this.renderedNodes.clear(); + this.disposables = dispose(this.disposables); + } } function getLocation(node: ITreeNode): number[] { @@ -113,11 +140,16 @@ export class Tree implements IDisposable { options?: IListOptions ) { const treeDelegate = new TreeDelegate(delegate); - const treeRenderers = renderers.map(r => new TreeRenderer(r)); + + const onDidChangeCollapseStateRelay = new Relay>(); + const treeRenderers = renderers.map(r => new TreeRenderer(r, onDidChangeCollapseStateRelay.event)); + this.disposables.push(...treeRenderers); + const treeOptions = toTreeListOptions(options); this.view = new List(container, treeDelegate, treeRenderers, treeOptions); this.model = new TreeModel(this.view); + onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState; this.view.onMouseClick(this.onMouseClick, this, this.disposables); } diff --git a/src/vs/base/browser/ui/tree/treeModel.ts b/src/vs/base/browser/ui/tree/treeModel.ts index 98a3247b625531280cfc1e1f6eb26ddd67a49374..9e9f103e4ea99cb5178b6b84efc467ce7a211887 100644 --- a/src/vs/base/browser/ui/tree/treeModel.ts +++ b/src/vs/base/browser/ui/tree/treeModel.ts @@ -8,6 +8,7 @@ import { ISpliceable } from 'vs/base/common/sequence'; import { IIterator, map, collect, iter, empty } from 'vs/base/common/iterator'; import { last } from 'vs/base/common/arrays'; +import { Emitter, Event } from 'vs/base/common/event'; export interface ITreeElement { readonly element: T; @@ -93,6 +94,9 @@ export class TreeModel { visibleCount: 1 }; + private _onDidChangeCollapseState = new Emitter>(); + readonly onDidChangeCollapseState: Event> = this._onDidChangeCollapseState.event; + constructor(private list: ISpliceable>) { } splice(location: number[], deleteCount: number, toInsert?: IIterator> | ITreeElement[]): IIterator> { @@ -125,7 +129,7 @@ export class TreeModel { } private _setCollapsed(location: number[], collapsed?: boolean | undefined): void { - let { node, listIndex, visible } = this.findNode(location); + const { node, listIndex, visible } = this.findNode(location); if (typeof collapsed === 'undefined') { collapsed = !node.collapsed; @@ -143,19 +147,23 @@ export class TreeModel { if (collapsed) { const deleteCount = getVisibleCount(node.children); - this.list.splice(listIndex, 1 + deleteCount, [node]); + this.list.splice(listIndex + 1, deleteCount, []); visibleCountDiff = -deleteCount; } else { - const toInsert = [node, ...getVisibleNodes(node.children)]; + const toInsert = getVisibleNodes(node.children); - this.list.splice(listIndex, 1, toInsert); - visibleCountDiff = toInsert.length - 1; + this.list.splice(listIndex + 1, 0, toInsert); + visibleCountDiff = toInsert.length; } - while (node) { - node.visibleCount += visibleCountDiff; - node = node.parent; + let mutableNode = node; + + while (mutableNode) { + mutableNode.visibleCount += visibleCountDiff; + mutableNode = mutableNode.parent; } + + this._onDidChangeCollapseState.fire(node); } }