From 1b77525993ccdd82a40b7a4e3ba874c5a2eb7a5a Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 20 Nov 2017 11:45:15 +0100 Subject: [PATCH] fixes #38628 --- src/vs/base/browser/ui/list/listView.ts | 38 ++++++++++++++------ src/vs/base/browser/ui/list/listWidget.ts | 36 ++++++++++++++----- src/vs/base/browser/ui/list/rangeMap.ts | 6 ---- src/vs/base/browser/ui/list/rowCache.ts | 43 +++++++++++++---------- 4 files changed, 80 insertions(+), 43 deletions(-) diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index d1fd07fd69e..0cc6ed66818 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { toObject, assign, getOrDefault } from 'vs/base/common/objects'; +import { assign, getOrDefault } from 'vs/base/common/objects'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import * as DOM from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollEvent, ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { RangeMap, IRange, relativeComplement, each } from './rangeMap'; +import { RangeMap, IRange, relativeComplement } from './rangeMap'; import { IDelegate, IRenderer, ISpliceable } from './list'; import { RowCache, IRow } from './rowCache'; import { isWindows } from 'vs/base/common/platform'; @@ -72,7 +72,7 @@ export class ListView implements ISpliceable, IDisposable { private itemId: number; private rangeMap: RangeMap; private cache: RowCache; - private renderers: { [templateId: string]: IRenderer; }; + private renderers = new Map>(); private lastRenderTop: number; private lastRenderHeight: number; private _domNode: HTMLElement; @@ -90,7 +90,11 @@ export class ListView implements ISpliceable, IDisposable { this.items = []; this.itemId = 0; this.rangeMap = new RangeMap(); - this.renderers = toObject>(renderers, r => r.templateId); + + for (const renderer of renderers) { + this.renderers.set(renderer.templateId, renderer); + } + this.cache = new RowCache(this.renderers); this.lastRenderTop = 0; @@ -127,7 +131,10 @@ export class ListView implements ISpliceable, IDisposable { splice(start: number, deleteCount: number, elements: T[] = []): T[] { const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); - each(previousRenderRange, i => this.removeItemFromDOM(this.items[i])); + + for (let i = previousRenderRange.start; i < previousRenderRange.end; i++) { + this.removeItemFromDOM(this.items[i]); + } const inserted = elements.map>(element => ({ id: String(this.itemId++), @@ -140,9 +147,11 @@ export class ListView implements ISpliceable, IDisposable { this.rangeMap.splice(start, deleteCount, ...inserted); const deleted = this.items.splice(start, deleteCount, ...inserted); - const renderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); - each(renderRange, i => this.insertItemInDOM(this.items[i], i)); + + for (let i = renderRange.start; i < renderRange.end; i++) { + this.insertItemInDOM(this.items[i], i); + } const scrollHeight = this.getContentHeight(); this.rowsContainer.style.height = `${scrollHeight}px`; @@ -200,8 +209,17 @@ export class ListView implements ISpliceable, IDisposable { const rangesToInsert = relativeComplement(renderRange, previousRenderRange); const rangesToRemove = relativeComplement(previousRenderRange, renderRange); - rangesToInsert.forEach(range => each(range, i => this.insertItemInDOM(this.items[i], i))); - rangesToRemove.forEach(range => each(range, i => this.removeItemFromDOM(this.items[i]))); + for (const range of rangesToInsert) { + for (let i = range.start; i < range.end; i++) { + this.insertItemInDOM(this.items[i], i); + } + } + + for (const range of rangesToRemove) { + for (let i = range.start; i < range.end; i++) { + this.removeItemFromDOM(this.items[i], ); + } + } if (canUseTranslate3d() && !isWindows /* Windows: translate3d breaks subpixel-antialias (ClearType) unless a background is defined */) { const transform = `translate3d(0px, -${renderTop}px, 0px)`; @@ -226,7 +244,7 @@ export class ListView implements ISpliceable, IDisposable { this.rowsContainer.appendChild(item.row.domNode); } - const renderer = this.renderers[item.templateId]; + const renderer = this.renderers.get(item.templateId); item.row.domNode.style.top = `${this.elementTop(index)}px`; item.row.domNode.style.height = `${item.size}px`; item.row.domNode.setAttribute('data-index', `${index}`); diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index ce3d89f43ba..baade6ee70c 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -30,7 +30,9 @@ class CombinedSpliceable implements ISpliceable { constructor(private spliceables: ISpliceable[]) { } splice(start: number, deleteCount: number, elements: T[]): void { - this.spliceables.forEach(s => s.splice(start, deleteCount, elements)); + for (const spliceable of this.spliceables) { + spliceable.splice(start, deleteCount, elements); + } } } @@ -74,15 +76,19 @@ class TraitRenderer implements IRenderer } renderIndexes(indexes: number[]): void { - this.rendered - .filter(({ index }) => indexes.indexOf(index) > -1) - .forEach(({ index, templateData }) => this.trait.renderIndex(index, templateData.container)); + for (const { index, templateData } of this.rendered) { + if (indexes.indexOf(index) > -1) { + this.trait.renderIndex(index, templateData.container); + } + } } splice(start: number, deleteCount: number): void { - this.rendered - .filter(({ index }) => index >= start && index < start + deleteCount) - .forEach(({ templateData }) => templateData.elementDisposable.dispose()); + for (const { index, templateData } of this.rendered) { + if (index >= start && index < start + deleteCount) { + templateData.elementDisposable.dispose(); + } + } } disposeTemplate(templateData: ITraitTemplateData): void { @@ -583,11 +589,19 @@ class PipelineRenderer implements IRenderer { } renderElement(element: T, index: number, templateData: any[]): void { - this.renderers.forEach((r, i) => r.renderElement(element, index, templateData[i])); + let i = 0; + + for (const renderer of this.renderers) { + renderer.renderElement(element, index, templateData[i++]); + } } disposeTemplate(templateData: any[]): void { - this.renderers.forEach((r, i) => r.disposeTemplate(templateData[i])); + let i = 0; + + for (const renderer of this.renderers) { + renderer.disposeTemplate(templateData[i]); + } } } @@ -689,6 +703,10 @@ export class List implements ISpliceable, IDisposable { } splice(start: number, deleteCount: number, elements: T[] = []): void { + if (deleteCount === 0 && elements.length === 0) { + return; + } + this.eventBufferer.bufferEvents(() => this.spliceable.splice(start, deleteCount, elements)); } diff --git a/src/vs/base/browser/ui/list/rangeMap.ts b/src/vs/base/browser/ui/list/rangeMap.ts index e100122c8db..105501e5e71 100644 --- a/src/vs/base/browser/ui/list/rangeMap.ts +++ b/src/vs/base/browser/ui/list/rangeMap.ts @@ -56,12 +56,6 @@ export function relativeComplement(one: IRange, other: IRange): IRange[] { return result; } -export function each(range: IRange, fn: (index: number) => void): void { - for (let i = range.start; i < range.end; i++) { - fn(i); - } -} - /** * Returns the intersection between a ranged group and a range. * Returns `[]` if the intersection is empty. diff --git a/src/vs/base/browser/ui/list/rowCache.ts b/src/vs/base/browser/ui/list/rowCache.ts index 0acf71e4340..7dc3d0f720d 100644 --- a/src/vs/base/browser/ui/list/rowCache.ts +++ b/src/vs/base/browser/ui/list/rowCache.ts @@ -23,11 +23,9 @@ function removeFromParent(element: HTMLElement): void { export class RowCache implements IDisposable { - private cache: { [templateId: string]: IRow[]; }; + private cache = new Map(); - constructor(private renderers: { [templateId: string]: IRenderer; }) { - this.cache = Object.create(null); - } + constructor(private renderers: Map>) { } /** * Returns a row either by creating a new one or reusing @@ -38,7 +36,7 @@ export class RowCache implements IDisposable { if (!result) { const domNode = $('.monaco-list-row'); - const renderer = this.renderers[templateId]; + const renderer = this.renderers.get(templateId); const templateData = renderer.renderTemplate(domNode); result = { domNode, templateId, templateData }; } @@ -67,27 +65,36 @@ export class RowCache implements IDisposable { } private getTemplateCache(templateId: string): IRow[] { - return this.cache[templateId] || (this.cache[templateId] = []); + let result = this.cache.get(templateId); + + if (!result) { + result = []; + this.cache.set(templateId, result); + } + + return result; } private garbageCollect(): void { - if (this.cache) { - Object.keys(this.cache).forEach(templateId => { - this.cache[templateId].forEach(cachedRow => { - const renderer = this.renderers[templateId]; - renderer.disposeTemplate(cachedRow.templateData); - cachedRow.domNode = null; - cachedRow.templateData = null; - }); - - delete this.cache[templateId]; - }); + if (!this.renderers) { + return; } + + this.cache.forEach((cachedRows, templateId) => { + for (const cachedRow of cachedRows) { + const renderer = this.renderers[templateId]; + renderer.disposeTemplate(cachedRow.templateData); + cachedRow.domNode = null; + cachedRow.templateData = null; + } + }); + + this.cache.clear(); } dispose(): void { this.garbageCollect(); - this.cache = null; + this.cache.clear(); this.renderers = null; } } \ No newline at end of file