diff --git a/src/vs/base/browser/ui/list/listImpl.ts b/src/vs/base/browser/ui/list/listImpl.ts index 9cea31a7d90d766c9709720f4b6ab98514bf7ee9..0db05052264bb7fe4666c29701c91706f5fd5dce 100644 --- a/src/vs/base/browser/ui/list/listImpl.ts +++ b/src/vs/base/browser/ui/list/listImpl.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./list'; import { IScrollable } from 'vs/base/common/scrollable'; import Event, { Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -14,15 +15,16 @@ import { RangeMap } from './rangeMap'; import { IScrollEvent, IDelegate, IRendererMap } from './list'; import { RowCache, IRow } from './rowCache'; -interface IItem { - height: number; +interface IItem { + element: T; + size: number; templateId: string; row: IRow; } export class List implements IScrollable { - private items: IItem[]; + private items: IItem[]; private rangeMap: RangeMap; private cache: RowCache; @@ -40,7 +42,11 @@ export class List implements IScrollable { private _onScroll = new Emitter(); onScroll: Event = this._onScroll.event; - constructor(container: HTMLElement, delegate: IDelegate, renderers: IRendererMap) { + constructor( + container: HTMLElement, + private delegate: IDelegate, + private renderers: IRendererMap + ) { this.items = []; this.rangeMap = new RangeMap(); this.cache = new RowCache(renderers); @@ -64,6 +70,35 @@ export class List implements IScrollable { this.rowsContainer = document.createElement('div'); this.rowsContainer.className = 'monaco-list-rows'; + + this.wrapper.appendChild(this.rowsContainer); + this.domNode.appendChild(this.scrollableElement.getDomNode()); + container.appendChild(this.domNode); + + this._scrollTop = 0; + this._viewHeight = 0; + this.renderTop = 0; + this.renderHeight = 0; + + this.layout(); + } + + splice(start: number, deleteCount: number, ...elements: T[]): void { + const inserted = elements.map>(element => ({ + element, + size: this.delegate.getHeight(element), + templateId: this.delegate.getTemplateId(element), + row: null + })); + + this.rangeMap.splice(start, deleteCount, ...inserted); + + const deleted = this.items.splice(start, deleteCount, ...inserted); + deleted.forEach(item => this.removeItemFromDOM(item)); + inserted.forEach((_, index) => this.insertItemInDOM(start + index)); + + this.setScrollTop(this.scrollTop); + this.scrollableElement.onElementInternalDimensions(); } layout(height?: number): void { @@ -120,7 +155,7 @@ export class List implements IScrollable { return this._viewHeight; } - private set viewHeight(viewHeight: number) { + private set viewHeight(viewHeight: number) { this.render(this.scrollTop, viewHeight); this._viewHeight = viewHeight; } @@ -135,6 +170,10 @@ export class List implements IScrollable { // Render + private indexAfter(position: number): number { + return Math.min(this.rangeMap.indexAt(position) + 1, this.rangeMap.count); + } + private render(scrollTop: number, viewHeight: number): void { const renderTop = Math.max(scrollTop, 0); const renderBottom = scrollTop + viewHeight; @@ -153,12 +192,12 @@ export class List implements IScrollable { // when view scrolls down, start unrendering from renderTop for (i = this.rangeMap.indexAt(this.renderTop), stop = Math.min(this.rangeMap.indexAt(renderTop), this.indexAfter(thisRenderBottom)); i < stop; i++) { - this.removeItemFromDOM(i); + this.removeItemFromDOM(this.items[i]); } // when view scrolls up, start unrendering from either renderBottom this.renderTop for (i = Math.max(this.indexAfter(renderBottom), this.rangeMap.indexAt(this.renderTop)), stop = this.indexAfter(thisRenderBottom); i < stop; i++) { - this.removeItemFromDOM(i); + this.removeItemFromDOM(this.items[i]); } const topPosition = this.rangeMap.positionAt(this.rangeMap.indexAt(renderTop)); @@ -171,11 +210,29 @@ export class List implements IScrollable { this.renderHeight = renderBottom - renderTop; } - private indexAfter(position: number): number { - return Math.min(this.rangeMap.indexAt(position) + 1, this.rangeMap.size); + private isInView(index: number): boolean { + const item = this.items[index]; + const top = this.rangeMap.positionAt(index); + return top < this.renderTop + this.renderHeight && top + item.size > this.renderTop; + } + + private refreshItem(index: number): void { + if (index < 0) { + return; + } + + if (this.isInView(index)) { + this.insertItemInDOM(index); + } else { + this.removeItemFromDOM(this.items[index]); + } } private insertItemInDOM(index: number): void { + if (index < 0) { + return; + } + const item = this.items[index]; if (!item.row) { @@ -200,10 +257,8 @@ export class List implements IScrollable { this.renderItem(index); } - private removeItemFromDOM(index: number): void { - const item = this.items[index]; - - if (!item.row) { + private removeItemFromDOM(item: IItem): void { + if (!item || !item.row) { return; } @@ -213,7 +268,11 @@ export class List implements IScrollable { } private renderItem(index: number): void { - // TODO + const item = this.items[index]; + const renderer = this.renderers[item.templateId]; + + item.row.domNode.style.height = `${ item.size }px`; + renderer.renderElement(item.element, item.row.templateData); } dispose() { diff --git a/src/vs/base/browser/ui/list/rangeMap.ts b/src/vs/base/browser/ui/list/rangeMap.ts index cb5b87cb8f8300fa2e63f0c270b16f1ee4252ed4..9e3e36d7f4332487d87675a49ce860450f19d5e7 100644 --- a/src/vs/base/browser/ui/list/rangeMap.ts +++ b/src/vs/base/browser/ui/list/rangeMap.ts @@ -161,17 +161,18 @@ export class RangeMap { let size = 0; for (const group of this.groups) { - const newSize = size + ((group.range.end - group.range.start) * group.size); + const count = group.range.end - group.range.start; + const newSize = size + (count * group.size); if (position < newSize) { return index + Math.floor((position - size) / group.size); } - index += group.size; + index += count; size = newSize; } - return -1; + return index; } /** diff --git a/src/vs/base/browser/ui/list/rowCache.ts b/src/vs/base/browser/ui/list/rowCache.ts index d0ea9292542137b1d94fe4618ebf8048c9da547e..ce96d6c59cc99c30f644c6ba739112da3d50e315 100644 --- a/src/vs/base/browser/ui/list/rowCache.ts +++ b/src/vs/base/browser/ui/list/rowCache.ts @@ -44,7 +44,7 @@ export class RowCache implements IDisposable { let result = this.getTemplateCache(templateId).pop(); if (!result) { - const domNode = $('div'); + const domNode = $('.monaco-list-row'); const content = append(domNode, $('.content')); const renderer = this.renderers[templateId]; const templateData = renderer.renderTemplate(content); diff --git a/src/vs/base/browser/ui/list/test/rangeMap.test.ts b/src/vs/base/browser/ui/list/test/rangeMap.test.ts index 57735e623936015a837750413db9973b20b6be2a..d8bf270ea4b3f05cddd36b826a10e8a8558c75a0 100644 --- a/src/vs/base/browser/ui/list/test/rangeMap.test.ts +++ b/src/vs/base/browser/ui/list/test/rangeMap.test.ts @@ -268,8 +268,8 @@ suite('RangeMap', () => { suite('indexAt, positionAt', () => { test('empty', () => { - assert.equal(rangeMap.indexAt(0), -1); - assert.equal(rangeMap.indexAt(10), -1); + assert.equal(rangeMap.indexAt(0), 0); + assert.equal(rangeMap.indexAt(10), 0); assert.equal(rangeMap.indexAt(-1), -1); assert.equal(rangeMap.positionAt(0), -1); assert.equal(rangeMap.positionAt(10), -1); @@ -279,7 +279,7 @@ suite('RangeMap', () => { test('simple', () => { rangeMap.splice(0, 0, one); assert.equal(rangeMap.indexAt(0), 0); - assert.equal(rangeMap.indexAt(1), -1); + assert.equal(rangeMap.indexAt(1), 1); assert.equal(rangeMap.positionAt(0), 0); assert.equal(rangeMap.positionAt(1), -1); }); @@ -289,7 +289,7 @@ suite('RangeMap', () => { assert.equal(rangeMap.indexAt(0), 0); assert.equal(rangeMap.indexAt(5), 0); assert.equal(rangeMap.indexAt(9), 0); - assert.equal(rangeMap.indexAt(10), -1); + assert.equal(rangeMap.indexAt(10), 1); assert.equal(rangeMap.positionAt(0), 0); assert.equal(rangeMap.positionAt(1), -1); }); @@ -300,12 +300,14 @@ suite('RangeMap', () => { assert.equal(rangeMap.indexAt(1), 1); assert.equal(rangeMap.indexAt(5), 5); assert.equal(rangeMap.indexAt(9), 9); - assert.equal(rangeMap.indexAt(10), -1); + assert.equal(rangeMap.indexAt(10), 10); + assert.equal(rangeMap.indexAt(11), 10); rangeMap.splice(10, 0, one, one, one, one, one, one, one, one, one, one); assert.equal(rangeMap.indexAt(10), 10); assert.equal(rangeMap.indexAt(19), 19); - assert.equal(rangeMap.indexAt(20), -1); + assert.equal(rangeMap.indexAt(20), 20); + assert.equal(rangeMap.indexAt(21), 20); assert.equal(rangeMap.positionAt(0), 0); assert.equal(rangeMap.positionAt(1), 1); assert.equal(rangeMap.positionAt(19), 19); @@ -319,7 +321,8 @@ suite('RangeMap', () => { assert.equal(rangeMap.indexAt(0), 0); assert.equal(rangeMap.indexAt(1), 1); assert.equal(rangeMap.indexAt(3), 3); - assert.equal(rangeMap.indexAt(4), -1); + assert.equal(rangeMap.indexAt(4), 4); + assert.equal(rangeMap.indexAt(5), 4); assert.equal(rangeMap.positionAt(0), 0); assert.equal(rangeMap.positionAt(1), 1); assert.equal(rangeMap.positionAt(3), 3); @@ -333,7 +336,8 @@ suite('RangeMap', () => { assert.equal(rangeMap.indexAt(0), 0); assert.equal(rangeMap.indexAt(1), 0); assert.equal(rangeMap.indexAt(30), 3); - assert.equal(rangeMap.indexAt(40), -1); + assert.equal(rangeMap.indexAt(40), 4); + assert.equal(rangeMap.indexAt(50), 4); assert.equal(rangeMap.positionAt(0), 0); assert.equal(rangeMap.positionAt(1), 10); assert.equal(rangeMap.positionAt(2), 20);