Skip to content

Commit ca7a790

Browse files
committed
list: incremental dom access
related to microsoft#37819
1 parent 38f6957 commit ca7a790

3 files changed

Lines changed: 51 additions & 15 deletions

File tree

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

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import * as DOM from 'vs/base/browser/dom';
1010
import { domEvent } from 'vs/base/browser/event';
1111
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
1212
import { ScrollEvent, ScrollbarVisibility } from 'vs/base/common/scrollable';
13-
import { RangeMap, IRange, relativeComplement } from './rangeMap';
13+
import { RangeMap, IRange, relativeComplement, intersect, shift } from './rangeMap';
1414
import { IDelegate, IRenderer } from './list';
1515
import { RowCache, IRow } from './rowCache';
1616
import { isWindows } from 'vs/base/common/platform';
@@ -132,11 +132,17 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
132132

133133
splice(start: number, deleteCount: number, elements: T[] = []): T[] {
134134
const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
135+
const deleteRange = { start, end: start + deleteCount };
136+
const removeRange = intersect(previousRenderRange, deleteRange);
135137

136-
for (let i = previousRenderRange.start; i < previousRenderRange.end; i++) {
138+
for (let i = removeRange.start; i < removeRange.end; i++) {
137139
this.removeItemFromDOM(this.items[i]);
138140
}
139141

142+
const previousRestRange: IRange = { start: start + deleteCount, end: this.items.length };
143+
const previousRenderedRestRange = intersect(previousRestRange, previousRenderRange);
144+
const previousUnrenderedRestRanges = relativeComplement(previousRestRange, previousRenderRange);
145+
140146
const inserted = elements.map<IItem<T>>(element => ({
141147
id: String(this.itemId++),
142148
element,
@@ -146,12 +152,37 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
146152
}));
147153

148154
this.rangeMap.splice(start, deleteCount, ...inserted);
149-
150155
const deleted = this.items.splice(start, deleteCount, ...inserted);
156+
157+
const delta = elements.length - deleteCount;
151158
const renderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
159+
const renderedRestRange = shift(previousRenderedRestRange, delta);
160+
const updateRange = intersect(renderRange, renderedRestRange);
161+
162+
for (let i = updateRange.start; i < updateRange.end; i++) {
163+
this.updateItemInDOM(this.items[i], i);
164+
}
165+
166+
const removeRanges = relativeComplement(renderedRestRange, renderRange);
167+
168+
for (let r = 0; r < removeRanges.length; r++) {
169+
const removeRange = removeRanges[r];
170+
171+
for (let i = removeRange.start; i < removeRange.end; i++) {
172+
this.removeItemFromDOM(this.items[i]);
173+
}
174+
}
175+
176+
const unrenderedRestRanges = previousUnrenderedRestRanges.map(r => shift(r, delta));
177+
const elementsRange = { start, end: start + elements.length };
178+
const insertRanges = [elementsRange, ...unrenderedRestRanges].map(r => intersect(renderRange, r));
179+
180+
for (let r = 0; r < insertRanges.length; r++) {
181+
const insertRange = insertRanges[r];
152182

153-
for (let i = renderRange.start; i < renderRange.end; i++) {
154-
this.insertItemInDOM(this.items[i], i);
183+
for (let i = insertRange.start; i < insertRange.end; i++) {
184+
this.insertItemInDOM(this.items[i], i);
185+
}
155186
}
156187

157188
const scrollHeight = this.getContentHeight();
@@ -252,6 +283,11 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
252283
renderer.renderElement(item.element, index, item.row.templateData);
253284
}
254285

286+
private updateItemInDOM(item: IItem<T>, index: number): void {
287+
item.row.domNode.style.top = `${this.elementTop(index)}px`;
288+
item.row.domNode.setAttribute('data-index', `${index}`);
289+
}
290+
255291
private removeItemFromDOM(item: IItem<T>): void {
256292
this.cache.release(item.row);
257293
item.row = null;

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@ export interface IRangedGroup {
1919

2020
/**
2121
* Returns the intersection between two ranges as a range itself.
22-
* Returns `null` if the intersection is empty.
22+
* Returns `{ start: 0, end: 0 }` if the intersection is empty.
2323
*/
2424
export function intersect(one: IRange, other: IRange): IRange {
2525
if (one.start >= other.end || other.start >= one.end) {
26-
return null;
26+
return { start: 0, end: 0 };
2727
}
2828

2929
const start = Math.max(one.start, other.start);
3030
const end = Math.min(one.end, other.end);
3131

3232
if (end - start <= 0) {
33-
return null;
33+
return { start: 0, end: 0 };
3434
}
3535

3636
return { start, end };
@@ -74,7 +74,7 @@ export function groupIntersect(range: IRange, groups: IRangedGroup[]): IRangedGr
7474

7575
const intersection = intersect(range, r.range);
7676

77-
if (!intersection) {
77+
if (isEmpty(intersection)) {
7878
continue;
7979
}
8080

@@ -90,7 +90,7 @@ export function groupIntersect(range: IRange, groups: IRangedGroup[]): IRangedGr
9090
/**
9191
* Shifts a range by that `much`.
9292
*/
93-
function shift({ start, end }: IRange, much: number): IRange {
93+
export function shift({ start, end }: IRange, much: number): IRange {
9494
return { start: start + much, end: end + much };
9595
}
9696

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ suite('RangeMap', () => {
1818
});
1919

2020
test('intersection', () => {
21-
assert.deepEqual(intersect({ start: 0, end: 0 }, { start: 0, end: 0 }), null);
22-
assert.deepEqual(intersect({ start: 0, end: 0 }, { start: 5, end: 5 }), null);
23-
assert.deepEqual(intersect({ start: 0, end: 1 }, { start: 5, end: 6 }), null);
24-
assert.deepEqual(intersect({ start: 5, end: 6 }, { start: 0, end: 1 }), null);
25-
assert.deepEqual(intersect({ start: 0, end: 5 }, { start: 2, end: 2 }), null);
21+
assert.deepEqual(intersect({ start: 0, end: 0 }, { start: 0, end: 0 }), { start: 0, end: 0 });
22+
assert.deepEqual(intersect({ start: 0, end: 0 }, { start: 5, end: 5 }), { start: 0, end: 0 });
23+
assert.deepEqual(intersect({ start: 0, end: 1 }, { start: 5, end: 6 }), { start: 0, end: 0 });
24+
assert.deepEqual(intersect({ start: 5, end: 6 }, { start: 0, end: 1 }), { start: 0, end: 0 });
25+
assert.deepEqual(intersect({ start: 0, end: 5 }, { start: 2, end: 2 }), { start: 0, end: 0 });
2626
assert.deepEqual(intersect({ start: 0, end: 1 }, { start: 0, end: 1 }), { start: 0, end: 1 });
2727
assert.deepEqual(intersect({ start: 0, end: 10 }, { start: 0, end: 5 }), { start: 0, end: 5 });
2828
assert.deepEqual(intersect({ start: 0, end: 5 }, { start: 0, end: 10 }), { start: 0, end: 5 });

0 commit comments

Comments
 (0)