diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 9a46ff9b4ed2cc2ecf099e7d078ce815dd260ea4..cc5c2bc5c93efda6ae4344b4640eed26379724e6 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -452,7 +452,7 @@ export class AsyncDataTree implements IDisposable const viewStateContext = viewState && { viewState, focus: [], selection: [] } as IAsyncDataTreeViewStateContext; - await this._updateChildren(input, true, viewStateContext); + await this._updateChildren(input, true, false, viewStateContext); if (viewStateContext) { this.tree.setFocus(viewStateContext.focus); @@ -464,11 +464,11 @@ export class AsyncDataTree implements IDisposable } } - async updateChildren(element: TInput | T = this.root.element, recursive = true): Promise { - await this._updateChildren(element, recursive); + async updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false): Promise { + await this._updateChildren(element, recursive, rerender); } - private async _updateChildren(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext): Promise { + private async _updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, viewStateContext?: IAsyncDataTreeViewStateContext): Promise { if (typeof this.root.element === 'undefined') { throw new TreeError(this.user, 'Tree input not set'); } @@ -478,7 +478,12 @@ export class AsyncDataTree implements IDisposable await Event.toPromise(this._onDidRender.event); } - await this.refreshAndRenderNode(this.getDataNode(element), recursive, viewStateContext); + const node = this.getDataNode(element); + await this.refreshAndRenderNode(node, recursive, viewStateContext); + + if (rerender) { + this.tree.rerender(node); + } } resort(element: TInput | T = this.root.element, recursive = true): void { diff --git a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts index 1177889e12af83a937e0824e83c5da00242ea6b7..95411fbbc71cb18e7b59bfdfa0db3d95c2f65ed6 100644 --- a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts @@ -12,19 +12,28 @@ import { timeout } from 'vs/base/common/async'; interface Element { id: string; + suffix?: string; children?: Element[]; } -function find(elements: Element[] | undefined, id: string): Element { - while (elements) { - for (const element of elements) { - if (element.id === id) { - return element; - } +function find(element: Element, id: string): Element | undefined { + if (element.id === id) { + return element; + } + + if (!element.children) { + return undefined; + } + + for (const child of element.children) { + const result = find(child, id); + + if (result) { + return result; } } - throw new Error('element not found'); + return undefined; } class Renderer implements ITreeRenderer { @@ -33,7 +42,7 @@ class Renderer implements ITreeRenderer { return container; } renderElement(element: ITreeNode, index: number, templateData: HTMLElement): void { - templateData.textContent = element.element.id; + templateData.textContent = element.element.id + (element.element.suffix || ''); } disposeTemplate(templateData: HTMLElement): void { // noop @@ -65,7 +74,13 @@ class Model { constructor(readonly root: Element) { } get(id: string): Element { - return find(this.root.children, id); + const result = find(this.root, id); + + if (!result) { + throw new Error('element not found'); + } + + return result; } } @@ -389,4 +404,36 @@ suite('AsyncDataTree', function () { assert(!hasClass(twistie, 'collapsible')); assert(!hasClass(twistie, 'collapsed')); }); + + test('issues #84569, #82629 - rerender', async () => { + const container = document.createElement('div'); + const model = new Model({ + id: 'root', + children: [{ + id: 'a', + children: [{ + id: 'b', + suffix: '1' + }] + }] + }); + + const tree = new AsyncDataTree('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() }); + tree.layout(200); + + await tree.setInput(model.root); + await tree.expand(model.get('a')); + assert.deepEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b1']); + + const a = model.get('a'); + const b = model.get('b'); + a.children?.splice(0, 1, { id: 'b', suffix: '2' }); + + await Promise.all([ + tree.updateChildren(a, true, true), + tree.updateChildren(b, true, true) + ]); + + assert.deepEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b2']); + }); }); diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index aaf81281f6f2b34d904b7abc6fd43ca92dbe29b1..8b08e2fc93aed0f0caebeb05176a8a20a9f9e295 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -614,16 +614,7 @@ export class CustomTreeView extends Disposable implements ITreeView { const tree = this.tree; if (tree && this.visible) { this.refreshing = true; - await Promise.all(elements.map(element => tree.updateChildren(element, true))); - - elements.map(element => { - try { - tree.rerender(element); - } catch { - // noop - } - }); - + await Promise.all(elements.map(element => tree.updateChildren(element, true, true))); this.refreshing = false; this.updateContentAreas(); if (this.focused) {