diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index aee77ac1234f98..c1050dd3ce57d8 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -1560,9 +1560,11 @@ export class ListView implements IListView { } private getVisibleRange(renderTop: number, renderHeight: number): IRange { + const start = this.rangeMap.indexAt(renderTop); return { - start: this.rangeMap.indexAt(renderTop), - end: this.rangeMap.indexAfter(renderTop + renderHeight - 1) + start, + // Clamp so a collapsed viewport (renderHeight <= 0) cannot produce an inverted range. + end: Math.max(start, this.rangeMap.indexAfter(renderTop + renderHeight - 1)) }; } diff --git a/src/vs/base/test/browser/ui/list/listView.test.ts b/src/vs/base/test/browser/ui/list/listView.test.ts index 98f603971a0762..4d5dc86be9b436 100644 --- a/src/vs/base/test/browser/ui/list/listView.test.ts +++ b/src/vs/base/test/browser/ui/list/listView.test.ts @@ -577,4 +577,29 @@ suite('ListView', function () { element.remove(); } }); + + test('does not throw when laid out with a collapsed viewport and zero-height dynamic items', function () { + type TestElement = { height: number }; + const delegate: IListVirtualDelegate = { + getHeight() { return 0; }, + getTemplateId() { return 'template'; }, + getDynamicHeight(element) { return element.height; } + }; + const renderer: IListRenderer = { + templateId: 'template', + renderTemplate() { }, + renderElement() { }, + disposeTemplate() { } + }; + + const elements: TestElement[] = [{ height: 0 }, { height: 0 }, { height: 0 }]; + const listView = new ListView(document.createElement('div'), delegate, [renderer], { supportDynamicHeights: true }); + try { + // Collapsing the viewport before splicing zero-height items previously yielded an inverted range that crashed probeDynamicHeights. + listView.layout(0, 200); + assert.doesNotThrow(() => listView.splice(0, 0, elements)); + } finally { + listView.dispose(); + } + }); });