diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 4d6bf592852..888b589fb8a 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -316,8 +316,12 @@ export abstract class AbstractTree implements IDisposable return this.model.setCollapsed(location, false); } - toggleCollapsed(location: TRef): void { - this.model.toggleCollapsed(location); + toggleCollapsed(location: TRef, recursive: boolean = false): void { + this.model.setCollapsed(location, undefined, recursive); + } + + expandAll(): void { + this.model.expandAll(); } collapseAll(): void { @@ -417,7 +421,9 @@ export abstract class AbstractTree implements IDisposable } const location = this.model.getNodeLocation(node); - this.model.toggleCollapsed(location); + const recursive = e.browserEvent.altKey; + + this.model.setCollapsed(location, undefined, recursive); } private onLeftArrow(e: StandardKeyboardEvent): void { @@ -487,7 +493,9 @@ export abstract class AbstractTree implements IDisposable const node = nodes[0]; const location = this.model.getNodeLocation(node); - this.model.toggleCollapsed(location); + const recursive = e.browserEvent.altKey; + + this.model.setCollapsed(location, undefined, recursive); } protected abstract createModel(view: ISpliceable>, options: IAbstractTreeOptions): ITreeModel; diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 1660d433644..705ce09cfe5 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -315,6 +315,10 @@ export class AsyncDataTree, TFilterData = void> imple this.tree.toggleCollapsed(this.getDataNode(element)); } + expandAll(): void { + this.tree.expandAll(); + } + collapseAll(): void { this.tree.collapseAll(); } diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index b1e7e788625..5973f91a302 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -46,7 +46,7 @@ export interface IIndexTreeModelOptions { export class IndexTreeModel, TFilterData = void> implements ITreeModel { private root: IMutableTreeNode; - private eventBufferer = new EventBufferer(); + private eventBufferer = new EventBufferer(); // TODO@joao is this really necessary private _onDidChangeCollapseState = new Emitter>(); readonly onDidChangeCollapseState: Event> = this.eventBufferer.wrapEvent(this._onDidChangeCollapseState.event); @@ -120,34 +120,59 @@ export class IndexTreeModel, TFilterData = voi } getListIndex(location: number[]): number { - const { node, listIndex } = this.getTreeNodeWithListIndex(location); - return node ? listIndex : -1; + return this.getTreeNodeWithListIndex(location).listIndex; } - setCollapsed(location: number[], collapsed: boolean): boolean { + setCollapsed(location: number[], collapsed?: boolean, recursive = false): boolean { const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location); - return this.eventBufferer.bufferEvents(() => this._setCollapsed(node, listIndex, revealed, collapsed)); + + if (typeof collapsed === 'undefined') { + collapsed = !node.collapsed; + } + + return this._setCollapsed(node, listIndex, revealed, collapsed, recursive); } - toggleCollapsed(location: number[]): void { - const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location); - this.eventBufferer.bufferEvents(() => this._setCollapsed(node, listIndex, revealed)); + private _setCollapsed(node: IMutableTreeNode, listIndex: number, revealed: boolean, collapsed: boolean, recursive: boolean): boolean { + const result = this._setNodeCollapsed(node, collapsed, recursive); + + if (!revealed || !node.visible) { + return result; + } + + const previousRenderNodeCount = node.renderNodeCount; + const toInsert = this.updateNodeAfterCollapseChange(node); + + const deleteCount = previousRenderNodeCount - (listIndex === -1 ? 0 : 1); + + this.list.splice(listIndex + 1, deleteCount, toInsert.slice(1)); + this._onDidChangeCollapseState.fire(node); + + return result; + } + + private _setNodeCollapsed(node: IMutableTreeNode, collapsed: boolean, recursive: boolean): boolean { + let result = node.collapsible && node.collapsed !== collapsed; + + if (node.collapsible) { + node.collapsed = collapsed; + } + + if (recursive) { + for (const child of node.children) { + result = this._setNodeCollapsed(child, collapsed, true) || result; + } + } + + return result; + } + + expandAll(): void { + this.setCollapsed([], false, true); } collapseAll(): void { - const queue = [...this.root.children]; - let listIndex = 0; - - this.eventBufferer.bufferEvents(() => { - while (queue.length > 0) { - const node = queue.shift()!; - const revealed = listIndex < this.root.children.length; - this._setCollapsed(node, listIndex, revealed, true); - - queue.push(...node.children); - listIndex++; - } - }); + this.setCollapsed([], true, true); } isCollapsible(location: number[]): boolean { @@ -164,32 +189,6 @@ export class IndexTreeModel, TFilterData = voi this.list.splice(0, previousRenderNodeCount, toInsert); } - private _setCollapsed(node: IMutableTreeNode, listIndex: number, revealed: boolean, collapsed?: boolean | undefined): boolean { - if (!node.collapsible) { - return false; - } - - if (typeof collapsed === 'undefined') { - collapsed = !node.collapsed; - } - - if (node.collapsed === collapsed) { - return false; - } - - node.collapsed = collapsed; - - if (revealed) { - const previousRenderNodeCount = node.renderNodeCount; - const toInsert = this.updateNodeAfterCollapseChange(node); - - this.list.splice(listIndex + 1, previousRenderNodeCount - 1, toInsert.slice(1)); - this._onDidChangeCollapseState.fire(node); - } - - return true; - } - private createTreeNode( treeElement: ITreeElement, parent: IMutableTreeNode, @@ -375,6 +374,10 @@ export class IndexTreeModel, TFilterData = voi // expensive private getTreeNodeWithListIndex(location: number[]): { node: IMutableTreeNode, listIndex: number, revealed: boolean } { + if (location.length === 0) { + return { node: this.root, listIndex: -1, revealed: true }; + } + const { parentNode, listIndex, revealed } = this.getParentNodeWithListIndex(location); const index = location[location.length - 1]; diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index b77c782647e..25f793562f5 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -111,9 +111,8 @@ export class ObjectTreeModel, TFilterData extends Non return this.model.setCollapsed(location, collapsed); } - toggleCollapsed(element: T): void { - const location = this.getElementLocation(element); - this.model.toggleCollapsed(location); + expandAll(): void { + this.model.expandAll(); } collapseAll(): void { diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 075dd493a0f..b510fd8898f 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -100,8 +100,8 @@ export interface ITreeModel { isCollapsible(location: TRef): boolean; isCollapsed(location: TRef): boolean; - setCollapsed(location: TRef, collapsed: boolean): boolean; - toggleCollapsed(location: TRef): void; + setCollapsed(location: TRef, collapsed?: boolean, recursive?: boolean): boolean; + expandAll(): void; collapseAll(): void; refilter(): void; diff --git a/test/tree/public/index.html b/test/tree/public/index.html index 0338fae23fd..3c8fafb5e4d 100644 --- a/test/tree/public/index.html +++ b/test/tree/public/index.html @@ -26,6 +26,7 @@ + @@ -179,6 +180,7 @@ case '?problems': { const { tree, treeFilter } = createIndexTree(); + expandall.onclick = () => perf('expand all', () => tree.expandAll()); collapseall.onclick = () => perf('collapse all', () => tree.collapseAll()); renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random())); @@ -199,6 +201,7 @@ case '?data': { const { tree, treeFilter } = createAsyncDataTree(); + expandall.onclick = () => perf('expand all', () => tree.expandAll()); collapseall.onclick = () => perf('collapse all', () => tree.collapseAll()); renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random())); refresh.onclick = () => perf('refresh', () => tree.refresh(null, true)); @@ -210,6 +213,7 @@ case '?height': { const { tree, treeFilter } = createIndexTree({ supportDynamicHeights: true }); + expandall.onclick = () => perf('expand all', () => tree.expandAll()); collapseall.onclick = () => perf('collapse all', () => tree.collapseAll()); renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random())); @@ -227,6 +231,7 @@ default: { const { tree, treeFilter } = createIndexTree(); + expandall.onclick = () => perf('expand all', () => tree.expandAll()); collapseall.onclick = () => perf('collapse all', () => tree.collapseAll()); renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random())); diff --git a/test/tree/server.js b/test/tree/server.js index 5431bc1c1cb..31bf2f57cc1 100644 --- a/test/tree/server.js +++ b/test/tree/server.js @@ -17,7 +17,7 @@ async function getTree(fsPath, level) { const element = path.basename(fsPath); const stat = await fs.stat(fsPath); - if (!stat.isDirectory() || element === '.git' || element === '.build' || level >= 2) { + if (!stat.isDirectory() || element === '.git' || element === '.build' || level >= 4) { return { element }; }