Change:
Ideas to experiment with: Don't shrink a cell while it is executing -less jumpy as output is replaced change the anchor heuristic from "if the resizing cell is partially visible" to "if the cell's output will shrink out of view" Anchor to a focused cell if the focus is in the cell's editor cc
For the new heuristics we are adding (growing upwards or downwards), they should probably be interrupted the moment users start to scroll, for example, if I scroll manually to a specific position, I would expect the scroll top not to change.
I disagree - That could mean if you have a cell focused and anchored, and you scroll to get it higher in the view, it would then start getting pushed down as the previous cell grows.
If I scroll to get it lower in the view, as I want to read the cells above, and since it's still in the view and focused/anchored, the cells I was reading will then pushed upwards, right? This feels unnatural compared to how browser handles scrolling or Terminal. Terminal scenario might be closer to this, as it usually sticks to the bottom when the shell is streaming outputs, but the very moment we scroll upwards, it stops sticking to the bottom.

Discussion:
ensureNoDisposablesAreLeakedInTestSuite(); <ins> let config: TestConfigurationService; </ins> setup(() => { testDisposables = new DisposableStore(); instantiationService = setupInstantiationService(testDisposables); <del> const config = new TestConfigurationService({ </del> <ins> config = new TestConfigurationService({ </ins> [NotebookSetting.anchorToFocusedCell]: 'auto' }); instantiationService.stub(IConfigurationService, config); });

}); }); <del> test('updateElementHeight with anchor #121723', async function () { </del> <ins> test('updateElementHeight with anchor', async function () { </ins> await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markup, [], {}],

assert.deepStrictEqual(cellList.getViewScrollBottom(), 210); // scroll to 5 <ins> cellList.updateElementHeight2(viewModel.cellAt(0)!, 50); </ins> cellList.scrollTop = 5; assert.deepStrictEqual(cellList.scrollTop, 5); assert.deepStrictEqual(cellList.getViewScrollBottom(), 215);

cellList.updateElementHeight2(viewModel.cellAt(0)!, 100); assert.deepStrictEqual(cellList.scrollHeight, 400); <del> // the first cell grows, but it's partially visible, so we won't push down the focused cell </del> <ins> // the first cell grows, but we anchor to the focused cell, so the notebook will scroll down </ins> assert.deepStrictEqual(cellList.scrollTop, 55); assert.deepStrictEqual(cellList.getViewScrollBottom(), 265); <ins> // We don't anchor to the focused cell when cells shrink </ins> cellList.updateElementHeight2(viewModel.cellAt(0)!, 50); <del> assert.deepStrictEqual(cellList.scrollTop, 5); assert.deepStrictEqual(cellList.getViewScrollBottom(), 215); </del> <ins> assert.deepStrictEqual(cellList.scrollTop, 55); assert.deepStrictEqual(cellList.getViewScrollBottom(), 265); </ins> // focus won't be visible after cell 0 grow to 250, so let's try to keep the focused cell visible cellList.updateElementHeight2(viewModel.cellAt(0)!, 250);

}); }); <del> test('updateElementHeight with anchor #121723: focus element out of viewport', async function () { </del> <ins> test('updateElementHeight with no scrolling', async function () { config.setUserConfiguration(NotebookSetting.scrollToRevealCell, 'none'); await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markup, [], {}], ['var b = 1;', 'javascript', CellKind.Code, [], {}], ['# header b', 'markdown', CellKind.Markup, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], cellTotalHeights: [50, 100, 50, 100, 50], cellLineNumberStates: {}, collapsedInputCells: {}, collapsedOutputCells: {}, }); const cellList = createNotebookCellList(instantiationService, disposables); cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell cellList.layout(210, 100); // init scrollTop and scrollBottom assert.deepStrictEqual(cellList.scrollTop, 0); assert.deepStrictEqual(cellList.getViewScrollBottom(), 210); // scroll to 5 cellList.updateElementHeight2(viewModel.cellAt(0)!, 50); cellList.scrollTop = 5; assert.deepStrictEqual(cellList.scrollTop, 5); assert.deepStrictEqual(cellList.getViewScrollBottom(), 215); cellList.setFocus([1]); cellList.updateElementHeight2(viewModel.cellAt(0)!, 100); assert.deepStrictEqual(cellList.scrollHeight, 400); // Any change in cell size should not affect the scroll height with scrollToReveal set to none assert.deepStrictEqual(cellList.scrollTop, 5); cellList.updateElementHeight2(viewModel.cellAt(0)!, 50); assert.deepStrictEqual(cellList.scrollTop, 5); cellList.updateElementHeight2(viewModel.cellAt(0)!, 250); assert.deepStrictEqual(cellList.scrollTop, 5); }); }); test('updateElementHeight with no scroll setting and cell editor focused', async function () { config.setUserConfiguration(NotebookSetting.scrollToRevealCell, 'none'); await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markup, [], {}], ['var b = 1;', 'javascript', CellKind.Code, [], {}], ['# header b', 'markdown', CellKind.Markup, [], {}], ['var b = 2;', 'javascript', CellKind.Code, [], {}], ['# header c', 'markdown', CellKind.Markup, [], {}] ], async (editor, viewModel, disposables) => { viewModel.restoreEditorViewState({ editingCells: [false, false, false, false, false], editorViewStates: [null, null, null, null, null], cellTotalHeights: [50, 100, 50, 100, 50], cellLineNumberStates: {}, collapsedInputCells: {}, collapsedOutputCells: {}, }); const cellList = createNotebookCellList(instantiationService, disposables); cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell cellList.layout(210, 100); // init scrollTop and scrollBottom assert.deepStrictEqual(cellList.scrollTop, 0); assert.deepStrictEqual(cellList.getViewScrollBottom(), 210); cellList.setFocus([1]); editor.focusNotebookCell(cellList.viewModel?.cellAt(1)!, 'editor'); cellList.updateElementHeight2(viewModel.cellAt(0)!, 100); assert.deepStrictEqual(cellList.scrollHeight, 400); // We have the cell editor focused, so we should anchor to that cell assert.deepStrictEqual(cellList.scrollTop, 50); cellList.updateElementHeight2(viewModel.cellAt(0)!, 50); assert.deepStrictEqual(cellList.scrollTop, 0); cellList.updateElementHeight2(viewModel.cellAt(0)!, 250); assert.deepStrictEqual(cellList.scrollTop, 250 + 100 - cellList.renderHeight); }); }); test('updateElementHeight with focused element out of viewport', async function () { </ins> await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markup, [], {}],

import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; <del> import { CellFindMatchWithIndex, IActiveNotebookEditorDelegate, IBaseCellEditorOptions, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; </del> <ins> import { CellFindMatchWithIndex, CellFocusMode, IActiveNotebookEditorDelegate, IBaseCellEditorOptions, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; </ins> import { NotebookCellStateChangedEvent, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { NotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/browser/services/notebookCellStatusBarServiceImpl'; import { ListViewInfoAccessor, NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList';

override async revealRangeInCenterIfOutsideViewportAsync() { } override async layoutNotebookCell() { } override async removeInset() { } <del> override async focusNotebookCell() { } </del> <ins> override async focusNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output') { cell.focusMode = focusItem === 'editor' ? CellFocusMode.Editor : focusItem === 'output' ? CellFocusMode.Output : CellFocusMode.Container; } </ins> override cellAt(index: number) { return viewModel.cellAt(index)!; } override getCellIndex(cell: ICellViewModel) { return viewModel.getCellIndex(cell); } override getCellsInRange(range?: ICellRange) { return viewModel.getCellsInRange(range); }