Skip to content

Commit b74d54a

Browse files
committed
introduce tree.refresh and refactor tree.updateChildren
fixes microsoft#65393
1 parent bc5b547 commit b74d54a

17 files changed

Lines changed: 83 additions & 120 deletions

File tree

src/vs/base/browser/ui/list/list.ts

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
import { GestureEvent } from 'vs/base/browser/touch';
77
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
8-
import { Event } from 'vs/base/common/event';
9-
import { IDisposable } from 'vs/base/common/lifecycle';
108
import { IDragAndDropData } from 'vs/base/browser/dnd';
119

1210
export interface IListVirtualDelegate<T> {
@@ -92,53 +90,3 @@ export interface IListDragAndDrop<T> {
9290
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction;
9391
drop(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void;
9492
}
95-
96-
/**
97-
* Use this renderer when you want to re-render elements on account of
98-
* an event firing.
99-
*/
100-
export abstract class AbstractListRenderer<T, TTemplateData> implements IListRenderer<T, TTemplateData> {
101-
102-
private renderedElements = new Map<T, TTemplateData>();
103-
private listener: IDisposable;
104-
105-
constructor(onDidChange: Event<T | T[] | undefined>) {
106-
this.listener = onDidChange(this.onDidChange, this);
107-
}
108-
109-
renderElement(element: T, index: number, templateData: TTemplateData): void {
110-
this.renderedElements.set(element, templateData);
111-
}
112-
113-
disposeElement(element: T, index: number, templateData: TTemplateData): void {
114-
this.renderedElements.delete(element);
115-
}
116-
117-
private onDidChange(e: T | T[] | undefined) {
118-
if (typeof e === 'undefined') {
119-
this.renderedElements.forEach((templateData, element) => this.renderElement(element, -1 /* TODO@joao */, templateData));
120-
} else if (Array.isArray(e)) {
121-
for (const element of e) {
122-
this.rerender(element);
123-
}
124-
} else {
125-
this.rerender(e);
126-
}
127-
}
128-
129-
private rerender(element: T): void {
130-
const templateData = this.renderedElements.get(element);
131-
132-
if (templateData) {
133-
this.renderElement(element, -1 /* TODO@Joao */, templateData);
134-
}
135-
}
136-
137-
dispose(): void {
138-
this.listener.dispose();
139-
}
140-
141-
abstract readonly templateId: string;
142-
abstract renderTemplate(container: HTMLElement): TTemplateData;
143-
abstract disposeTemplate(templateData: TTemplateData): void;
144-
}

src/vs/base/browser/ui/tree/asyncDataTree.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
351351
this.tree.style(styles);
352352
}
353353

354-
// Data Tree
354+
// Model
355355

356356
getInput(): TInput | undefined {
357357
return this.root.element as TInput;
@@ -365,22 +365,29 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
365365

366366
const viewStateContext = viewState && { viewState, focus: [], selection: [] } as IAsyncDataTreeViewStateContext<TInput, T>;
367367

368-
await this.refresh(input, true, viewStateContext);
368+
await this.updateChildren(input, true, viewStateContext);
369369

370370
if (viewStateContext) {
371371
this.tree.setFocus(viewStateContext.focus);
372372
this.tree.setSelection(viewStateContext.selection);
373373
}
374374
}
375375

376-
refresh(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
376+
updateChildren(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
377377
if (typeof this.root.element === 'undefined') {
378378
throw new Error('Tree input not set');
379379
}
380380

381381
return this.refreshNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh, viewStateContext);
382382
}
383383

384+
// View
385+
386+
refresh(element: T): void {
387+
const node = this.getDataNode(element);
388+
this.tree.refresh(node);
389+
}
390+
384391
// Tree
385392

386393
getNode(element: TInput | T = this.root.element): ITreeNode<TInput | T, TFilterData> {

src/vs/base/browser/ui/tree/dataTree.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
3838
this.identityProvider = options.identityProvider;
3939
}
4040

41+
// Model
42+
4143
getInput(): TInput | undefined {
4244
return this.input;
4345
}
@@ -76,14 +78,22 @@ export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | nu
7678
this.setSelection(selection);
7779
}
7880

79-
refresh(element: TInput | T = this.input!): void {
81+
updateChildren(element: TInput | T = this.input!): void {
8082
if (typeof this.input === 'undefined') {
8183
throw new Error('Tree input not set');
8284
}
8385

8486
this._refresh(element);
8587
}
8688

89+
// View
90+
91+
refresh(element: T): void {
92+
this.model.refresh(element);
93+
}
94+
95+
// Implementation
96+
8797
private _refresh(element: TInput | T, isCollapsed?: (el: T) => boolean): void {
8898
this.model.setChildren((element === this.input ? null : element) as T, this.createIterator(element, isCollapsed));
8999
}

src/vs/base/browser/ui/tree/indexTree.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterDat
3131
return this.model.splice(location, deleteCount, toInsert);
3232
}
3333

34+
refresh(location: number[]): void {
35+
this.model.refresh(location);
36+
}
37+
3438
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IIndexTreeOptions<T, TFilterData>): ITreeModel<T, TFilterData, number[]> {
3539
return new IndexTreeModel(view, this.rootElement, options);
3640
}

src/vs/base/browser/ui/tree/indexTreeModel.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,18 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi
126126
return Iterator.map(Iterator.fromArray(deletedNodes), treeNodeToElement);
127127
}
128128

129+
refresh(location: number[]): void {
130+
if (location.length === 0) {
131+
throw new Error('Invalid tree location');
132+
}
133+
134+
const { node, listIndex, revealed } = this.getTreeNodeWithListIndex(location);
135+
136+
if (revealed) {
137+
this.list.splice(listIndex, 1, [node]);
138+
}
139+
}
140+
129141
getListIndex(location: number[]): number {
130142
return this.getTreeNodeWithListIndex(location).listIndex;
131143
}

src/vs/base/browser/ui/tree/objectTree.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends
3636
return this.model.setChildren(element, children, onDidCreateNode, onDidDeleteNode);
3737
}
3838

39+
refresh(element: T): void {
40+
this.model.refresh(element);
41+
}
42+
3943
protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IObjectTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
4044
return new ObjectTreeModel(view, options);
4145
}

src/vs/base/browser/ui/tree/objectTreeModel.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non
103103
});
104104
}
105105

106+
refresh(element: T): void {
107+
const location = this.getElementLocation(element);
108+
this.model.refresh(location);
109+
}
110+
106111
getParentElement(ref: T | null = null): T | null {
107112
const location = this.getElementLocation(ref);
108113
return this.model.getParentElement(location);

src/vs/base/browser/ui/tree/tree.ts

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55

66
import { Event } from 'vs/base/common/event';
77
import { Iterator } from 'vs/base/common/iterator';
8-
import { IListRenderer, AbstractListRenderer, IListDragOverReaction, IListDragAndDrop, ListDragOverEffect } from 'vs/base/browser/ui/list/list';
8+
import { IListRenderer, IListDragOverReaction, IListDragAndDrop, ListDragOverEffect } from 'vs/base/browser/ui/list/list';
99
import { IDragAndDropData } from 'vs/base/browser/dnd';
10-
import { coalesce } from 'vs/base/common/arrays';
1110

1211
export const enum TreeVisibility {
1312

@@ -177,36 +176,3 @@ export const TreeDragOverReactions = {
177176
export interface ITreeDragAndDrop<T> extends IListDragAndDrop<T> {
178177
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction;
179178
}
180-
181-
/**
182-
* Use this renderer when you want to re-render elements on account of
183-
* an event firing.
184-
*/
185-
export abstract class AbstractTreeRenderer<T, TFilterData = void, TTemplateData = void>
186-
extends AbstractListRenderer<ITreeNode<T, TFilterData>, TTemplateData>
187-
implements ITreeRenderer<T, TFilterData, TTemplateData> {
188-
189-
private elementsToNodes = new Map<T, ITreeNode<T, TFilterData>>();
190-
191-
constructor(onDidChange: Event<T | T[] | undefined>) {
192-
super(Event.map(onDidChange, e => {
193-
if (typeof e === 'undefined') {
194-
return undefined;
195-
} else if (Array.isArray(e)) {
196-
return coalesce(e.map(e => this.elementsToNodes.get(e)));
197-
} else {
198-
return this.elementsToNodes.get(e);
199-
}
200-
}));
201-
}
202-
203-
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: TTemplateData): void {
204-
super.renderElement(node, index, templateData);
205-
this.elementsToNodes.set(node.element, node);
206-
}
207-
208-
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: TTemplateData): void {
209-
this.elementsToNodes.set(node.element, node);
210-
super.disposeElement(node, index, templateData);
211-
}
212-
}

src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,14 @@ suite('AsyncDataTree', function () {
9191
{ id: 'ac' }
9292
];
9393

94-
await tree.refresh(root);
94+
await tree.updateChildren(root);
9595
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
9696

9797
await tree.expand(_('a'));
9898
assert.equal(container.querySelectorAll('.monaco-list-row').length, 4);
9999

100100
_('a').children = [];
101-
await tree.refresh(root);
101+
await tree.updateChildren(root);
102102
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
103103
});
104104
});

src/vs/workbench/parts/debug/browser/baseDebugView.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { IExpression, IDebugService } from 'vs/workbench/parts/debug/common/debu
88
import { Expression, Variable } from 'vs/workbench/parts/debug/common/debugModel';
99
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
1010
import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
11-
import { ITreeRenderer, ITreeNode, AbstractTreeRenderer } from 'vs/base/browser/ui/tree/tree';
11+
import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
1212
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
1313
import { IThemeService } from 'vs/platform/theme/common/themeService';
1414
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
@@ -124,17 +124,13 @@ export interface IExpressionTemplateData {
124124
toDispose: IDisposable[];
125125
}
126126

127-
export abstract class AbstractExpressionsRenderer
128-
extends AbstractTreeRenderer<IExpression, void, IExpressionTemplateData>
129-
implements ITreeRenderer<IExpression, void, IExpressionTemplateData>, IDisposable {
127+
export abstract class AbstractExpressionsRenderer implements ITreeRenderer<IExpression, void, IExpressionTemplateData> {
130128

131129
constructor(
132130
@IDebugService protected debugService: IDebugService,
133131
@IContextViewService private readonly contextViewService: IContextViewService,
134132
@IThemeService private readonly themeService: IThemeService
135-
) {
136-
super(debugService.getViewModel().onDidSelectExpression);
137-
}
133+
) { }
138134

139135
abstract get templateId(): string;
140136

@@ -201,8 +197,6 @@ export abstract class AbstractExpressionsRenderer
201197
}
202198

203199
renderElement(node: ITreeNode<IExpression>, index: number, data: IExpressionTemplateData): void {
204-
super.renderElement(node, index, data);
205-
206200
const { element } = node;
207201
if (element === this.debugService.getViewModel().getSelectedExpression()) {
208202
data.enableInputBox(element, this.getInputBoxOptions(element));

0 commit comments

Comments
 (0)