Skip to content

fix(table): keep the viewport in place when stepping across the last row - #1054

Open
cabbagekobe wants to merge 2 commits into
charmbracelet:mainfrom
cabbagekobe:fix/table-moveup-offset-clamp
Open

cabbagekobe wants to merge 2 commits into
charmbracelet:mainfrom
cabbagekobe:fix/table-moveup-offset-clamp

Conversation

@cabbagekobe

@cabbagekobe cabbagekobe commented Sep 20, 2026 •

Copy link
Copy Markdown

Closes #1053.

Problem

With a table taller than its viewport, put the cursor on the last row (G / GotoBottom) and press k once. The whole visible window shifts up by one row and the cursor stays on the bottom line, instead of the cursor simply moving to the second-to-last line. The second k onward behaves normally.

Repro (30 rows, 5 visible): after GotoBottom the view shows r25..r29 with the cursor on r29; after MoveUp(1) it shows r24..r28 with the cursor on r28.

Cause

MoveUp computes the new y-offset and calls viewport.SetYOffset before UpdateViewport re-renders the row window. In v2 SetYOffset clamps to the current content, which still ends at the previous cursor: from the last row that content is only one row taller than the viewport, so the offset is cut back to 1 and the re-rendered window ends up shifted.

In v1 MoveUp assigned viewport.YOffset directly (no clamp), so the bug did not surface there. MoveDown already calls UpdateViewport first, which is why j is unaffected.

Fix

Call UpdateViewport() before SetYOffset() in MoveUp, mirroring MoveDown. The offset computation itself is unchanged.

TestMoveUpFromBottomKeepsViewport fails on main and passes with this change; the existing table tests still pass.

Follow-up: the same clamp bites MoveDown

After stepping up two or more rows from the last row, MoveDown(1) scrolled the window up by one row and the next MoveDown put it back. MoveDown re-renders first, and v2's SetContent clamps the y-offset when the window shrinks, so the step was subtracted from an already-clamped offset. The second commit reads the offset before UpdateViewport, mirroring the MoveUp change; TestMoveDownAfterUpsKeepsViewport fails on main and passes with it.

MoveUp applied the new viewport offset before UpdateViewport re-rendered
the row window, so viewport.SetYOffset clamped it against the previous
content. From the last row that content is only one row taller than the
viewport, so the offset was cut back and the first "up" shifted the whole
visible window by a row instead of moving just the cursor. Update the row
window first, as MoveDown already does.
MoveDown re-rendered the row window first and then subtracted its step
from the viewport's y-offset. In v2, SetContent clamps the y-offset when
the content shrinks, which it does as the window's start advances with
the cursor. After stepping up two or more rows from the last row, the
offset was therefore already reduced by SetContent, and MoveDown
reduced it again, scrolling the visible window up by a row instead of
just moving the cursor; the next MoveDown put it back.

Take the offset before UpdateViewport, as MoveUp already does, so the
step is applied to the offset the user actually saw. SetYOffset still
clamps the result at the end.
@cabbagekobe cabbagekobe changed the title fix(table): re-render rows before clamping the y-offset in MoveUp fix(table): keep the viewport in place when stepping across the last row Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

table (v2): moving up from the last row scrolls the whole window instead of the cursor

1 participant