Change:
Open Select the top cell and page down a few times Focus hits a cell that is longer than the viewport, which is maybe important, then the next page down moves to a higher cell
When a list item is larger than the viewport, and it's also the only element in the viewport, pressing page down will reveal to the previous element, this is because we did a here (the catch is, the last page element is also the first page element)

Discussion:
async focusNextPage(browserEvent?: UIEvent, filter?: (element: T) => boolean): Promise<void> { let lastPageIndex = this.view.indexAt(this.view.getScrollTop() + this.view.renderHeight); lastPageIndex = lastPageIndex === 0 ? 0 : lastPageIndex - 1; <del> const lastPageElement = this.view.element(lastPageIndex); </del> const currentlyFocusedElementIndex = this.getFocus()[0]; <del> const currentlyFocusedElement = this.view.element(currentlyFocusedElementIndex); if (currentlyFocusedElement !== lastPageElement && lastPageIndex > currentlyFocusedElementIndex) { </del> <ins> if (currentlyFocusedElementIndex !== lastPageIndex && (currentlyFocusedElementIndex === undefined || lastPageIndex > currentlyFocusedElementIndex)) { </ins> const lastGoodPageIndex = this.findPreviousIndex(lastPageIndex, false, filter); <del> if (lastGoodPageIndex > -1 && currentlyFocusedElement !== this.view.element(lastGoodPageIndex)) { </del> <ins> if (lastGoodPageIndex > -1 && currentlyFocusedElementIndex !== lastGoodPageIndex) { </ins> this.setFocus([lastGoodPageIndex], browserEvent); } else { this.setFocus([lastPageIndex], browserEvent);

firstPageIndex = this.view.indexAfter(scrollTop - 1); } <del> const firstPageElement = this.view.element(firstPageIndex); </del> const currentlyFocusedElementIndex = this.getFocus()[0]; <del> const currentlyFocusedElement = this.view.element(currentlyFocusedElementIndex); if (currentlyFocusedElement !== firstPageElement && currentlyFocusedElementIndex >= firstPageIndex) { </del> <ins> if (currentlyFocusedElementIndex !== firstPageIndex && (currentlyFocusedElementIndex === undefined || currentlyFocusedElementIndex >= firstPageIndex)) { </ins> const firstGoodPageIndex = this.findNextIndex(firstPageIndex, false, filter); <del> if (firstGoodPageIndex > -1 && currentlyFocusedElement !== this.view.element(firstGoodPageIndex)) { </del> <ins> if (firstGoodPageIndex > -1 && currentlyFocusedElementIndex !== firstGoodPageIndex) { </ins> this.setFocus([firstGoodPageIndex], browserEvent); } else { this.setFocus([firstPageIndex], browserEvent);

<ins> /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { range } from 'vs/base/common/arrays'; import { timeout } from 'vs/base/common/async'; suite('ListWidget', function () { test('Page up and down', async function () { const element = document.createElement('div'); element.style.height = '200px'; element.style.width = '200px'; const delegate: IListVirtualDelegate<number> = { getHeight() { return 20; }, getTemplateId() { return 'template'; } }; let templatesCount = 0; const renderer: IListRenderer<number, void> = { templateId: 'template', renderTemplate() { templatesCount++; }, renderElement() { }, disposeTemplate() { templatesCount--; } }; const listWidget = new List<number>('test', element, delegate, [renderer]); listWidget.layout(200); assert.strictEqual(templatesCount, 0, 'no templates have been allocated'); listWidget.splice(0, 0, range(100)); listWidget.focusFirst(); listWidget.focusNextPage(); assert.strictEqual(listWidget.getFocus()[0], 9, 'first page down moves focus to element at bottom'); // scroll to next page is async listWidget.focusNextPage(); await timeout(0); assert.strictEqual(listWidget.getFocus()[0], 19, 'page down to next page'); listWidget.focusPreviousPage(); assert.strictEqual(listWidget.getFocus()[0], 10, 'first page up moves focus to element at top'); // scroll to previous page is async listWidget.focusPreviousPage(); await timeout(0); assert.strictEqual(listWidget.getFocus()[0], 0, 'page down to previous page'); listWidget.dispose(); }); test('Page up and down with item taller than viewport #149502', async function () { const element = document.createElement('div'); element.style.height = '200px'; element.style.width = '200px'; const delegate: IListVirtualDelegate<number> = { getHeight() { return 200; }, getTemplateId() { return 'template'; } }; let templatesCount = 0; const renderer: IListRenderer<number, void> = { templateId: 'template', renderTemplate() { templatesCount++; }, renderElement() { }, disposeTemplate() { templatesCount--; } }; const listWidget = new List<number>('test', element, delegate, [renderer]); listWidget.layout(200); assert.strictEqual(templatesCount, 0, 'no templates have been allocated'); listWidget.splice(0, 0, range(100)); listWidget.focusFirst(); assert.strictEqual(listWidget.getFocus()[0], 0, 'initial focus is first element'); // scroll to next page is async listWidget.focusNextPage(); await timeout(0); assert.strictEqual(listWidget.getFocus()[0], 1, 'page down to next page'); // scroll to previous page is async listWidget.focusPreviousPage(); await timeout(0); assert.strictEqual(listWidget.getFocus()[0], 0, 'page up to next page'); listWidget.dispose(); }); }); </ins>