diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 54fabcf7def61b9c4467a8a0f4c734829e7e57f3..95bd1c3388679b67217c3f4d138ad0cef74e6d9a 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -418,6 +418,10 @@ export class AsyncDataTree implements IDisposable await this.refreshAndRenderNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh, viewStateContext); } + resort(element: TInput | T = this.root.element, recursive = true): void { + this.tree.resort(this.getDataNode(element), recursive); + } + hasNode(element: TInput | T): boolean { return element === this.root.element || this.nodes.has(element as T); } diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index d0379e7b612a3da6da1e947b638b6d319e0a2dc1..15541db5ee91bd88a8fec87c4fac0f8602c4ead8 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -89,6 +89,10 @@ export class DataTree extends AbstractTree, TFilterData = void> extends this.model.refresh(element); } + resort(element: T, recursive = true): void { + this.model.resort(element, recursive); + } + protected createModel(view: ISpliceable>, options: IObjectTreeOptions): ITreeModel { return new ObjectTreeModel(view, options); } diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index 7ba46c71031870021345564708e3b905562ed3a5..5a52c451aafc52cd39099f2d490186202948fc67 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -19,7 +19,7 @@ export class ObjectTreeModel, TFilterData extends Non private model: IndexTreeModel; private nodes = new Map>(); - private sorter?: ITreeSorter>; + private sorter?: ITreeSorter<{ element: T; }>; readonly onDidSplice: Event>; readonly onDidChangeCollapseState: Event>; @@ -49,6 +49,15 @@ export class ObjectTreeModel, TFilterData extends Non onDidDeleteNode?: (node: ITreeNode) => void ): Iterator> { const location = this.getElementLocation(element); + return this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode); + } + + private _setChildren( + location: number[], + children: ISequence> | undefined, + onDidCreateNode?: (node: ITreeNode) => void, + onDidDeleteNode?: (node: ITreeNode) => void + ): Iterator> { const insertedElements = new Set(); const _onDidCreateNode = (node: ITreeNode) => { @@ -73,13 +82,13 @@ export class ObjectTreeModel, TFilterData extends Non return this.model.splice( [...location, 0], Number.MAX_VALUE, - this.preserveCollapseState(children), + children, _onDidCreateNode, _onDidDeleteNode ); } - private preserveCollapseState(elements: ISequence> | undefined): ISequence> { + private preserveCollapseState(elements: ISequence> | undefined): ISequence> { let iterator = elements ? getSequenceIterator(elements) : Iterator.empty>(); if (this.sorter) { @@ -113,6 +122,32 @@ export class ObjectTreeModel, TFilterData extends Non this.model.refresh(location); } + resort(element: T | null = null, recursive = true): void { + if (!this.sorter) { + return; + } + + const location = this.getElementLocation(element); + const node = this.model.getNode(location); + + this._setChildren(location, this.resortChildren(node, recursive)); + } + + private resortChildren(node: ITreeNode, recursive: boolean, first = true): ISequence> { + let childrenNodes = Iterator.fromArray(node.children as ITreeNode[]); + + if (recursive || first) { + childrenNodes = Iterator.fromArray(Iterator.collect(childrenNodes).sort(this.sorter!.compare.bind(this.sorter))); + } + + return Iterator.map, ITreeElement>(childrenNodes, node => ({ + element: node.element as T, + collapsible: node.collapsible, + collapsed: node.collapsed, + children: this.resortChildren(node, recursive, false) + })); + } + getParentElement(ref: T | null = null): T | null { const location = this.getElementLocation(ref); return this.model.getParentElement(location); diff --git a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts index 59d0c0ef70f037c3d154dfc1ffc391f957753a8c..30cc43986a316d491a1c32ff77216d69aa8a945c 100644 --- a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts @@ -183,4 +183,41 @@ suite('ObjectTreeModel', function () { model.setChildren(null, data); assert.deepEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'compact', 'convertible', 'sedan']); }); + + test('resort', () => { + let compare: (a: string, b: string) => number = () => 0; + + const list: ITreeNode[] = []; + const model = new ObjectTreeModel(toSpliceable(list), { sorter: { compare(a, b) { return compare(a, b); } } }); + const data = [ + { element: 'cars', children: [{ element: 'sedan' }, { element: 'convertible' }, { element: 'compact' }] }, + { element: 'airplanes', children: [{ element: 'passenger' }, { element: 'jet' }] }, + { element: 'bicycles', children: [{ element: 'dutch' }, { element: 'mountain' }, { element: 'electric' }] }, + ]; + + model.setChildren(null, data); + assert.deepEqual(toArray(list), ['cars', 'sedan', 'convertible', 'compact', 'airplanes', 'passenger', 'jet', 'bicycles', 'dutch', 'mountain', 'electric']); + + // lexicographical + compare = (a, b) => a < b ? -1 : 1; + + // non-recursive + model.resort(null, false); + assert.deepEqual(toArray(list), ['airplanes', 'passenger', 'jet', 'bicycles', 'dutch', 'mountain', 'electric', 'cars', 'sedan', 'convertible', 'compact']); + + // recursive + model.resort(); + assert.deepEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'compact', 'convertible', 'sedan']); + + // reverse + compare = (a, b) => a < b ? 1 : -1; + + // scoped + model.resort('cars'); + assert.deepEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'sedan', 'convertible', 'compact']); + + // recursive + model.resort(); + assert.deepEqual(toArray(list), ['cars', 'sedan', 'convertible', 'compact', 'bicycles', 'mountain', 'electric', 'dutch', 'airplanes', 'passenger', 'jet']); + }); }); \ No newline at end of file