Scroll Kitty images with their scroll region #126

Merged
natsukium merged 2 commits from fix/scroll-region-images into main 2026-10-12 19:46:59 +09:00
Owner

Kitty graphics placements now scroll with the text inside a scroll region (DECSTBM) and on a reverse scroll (RI at the top margin, SD), following kitty's scroll_filter_margins_func:

  • A placement wholly inside the scrolled rows moves with them; one that crosses a margin or lies outside the region stays put.
  • One the scroll takes wholly out of the region is removed (PlacementRemoved).
  • One taken partly past a margin loses the rows past it: its extent shrinks (a requested r= with it) and its source rectangle loses the pixels those rows showed, at the scale the image draws at (kitty's src_pixels_per_screen_pixel, including the one-axis c=/r= cases). What is left keeps its size and position on screen.
  • The cursor advance after a placement at the bottom margin of a region goes through the same path.

The grid queues a new PtyEffect::RegionScrolled (adjacent scrolls of one band coalesce); the daemon re-sends each survivor as Placement, since PlacementsShifted moves every anchor. No wire change.

Two cases depart from kitty where its region path disagrees with its full-screen path, recorded in the explanation doc:

  • A region anchored at the primary screen's top pushes its rows into scrollback; its placements go into history whole along with those already there, as on a full-screen scroll. Kitty clips them and leaves history placements behind, off their text.
  • A full-screen reverse scroll uses the region rule. Kitty moves history placements down although no history text moves, and keeps those pushed off the bottom.

Tests: unit tests for move, removal, top clip, bottom clip with a partial last row, a lone-c= clip, placements crossing or outside the band, and the into-history case; a property test that survivors stay in the band, inside their image, at their old scale, and that placements outside the band are untouched; the image-mirror property test now exercises region scrolls and compares extents and source rectangles; a daemon test for the issue's repro.

Closes #40

Kitty graphics placements now scroll with the text inside a scroll region (`DECSTBM`) and on a reverse scroll (`RI` at the top margin, `SD`), following kitty's `scroll_filter_margins_func`: - A placement wholly inside the scrolled rows moves with them; one that crosses a margin or lies outside the region stays put. - One the scroll takes wholly out of the region is removed (`PlacementRemoved`). - One taken partly past a margin loses the rows past it: its extent shrinks (a requested `r=` with it) and its source rectangle loses the pixels those rows showed, at the scale the image draws at (kitty's `src_pixels_per_screen_pixel`, including the one-axis `c=`/`r=` cases). What is left keeps its size and position on screen. - The cursor advance after a placement at the bottom margin of a region goes through the same path. The grid queues a new `PtyEffect::RegionScrolled` (adjacent scrolls of one band coalesce); the daemon re-sends each survivor as `Placement`, since `PlacementsShifted` moves every anchor. No wire change. Two cases depart from kitty where its region path disagrees with its full-screen path, recorded in the explanation doc: - A region anchored at the primary screen's top pushes its rows into scrollback; its placements go into history whole along with those already there, as on a full-screen scroll. Kitty clips them and leaves history placements behind, off their text. - A full-screen reverse scroll uses the region rule. Kitty moves history placements down although no history text moves, and keeps those pushed off the bottom. Tests: unit tests for move, removal, top clip, bottom clip with a partial last row, a lone-`c=` clip, placements crossing or outside the band, and the into-history case; a property test that survivors stay in the band, inside their image, at their old scale, and that placements outside the band are untouched; the image-mirror property test now exercises region scrolls and compares extents and source rectangles; a daemon test for the issue's repro. Closes #40
grid, daemon: scroll Kitty placements with their scroll region
Some checks failed
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 8s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m10s
windows / detect relevant changes (pull_request) Successful in 10s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 3m46s
windows / frontend smoke (Windows) (pull_request) Successful in 1m27s
bench / instruction-count regression gate (pull_request) Has been cancelled
fuzz / cargo fuzz smoke (per target) (pull_request) Has been cancelled
pr / nix flake check (pull_request) Has been cancelled
pr / cargo build / clippy / test / deny (pull_request) Has been cancelled
pr / wire schema is compatible with the base (pull_request) Has been cancelled
pr / frontend smoke (x86_64-linux) (pull_request) Has been cancelled
windows / cargo clippy (Windows cross) (pull_request) Has been cancelled
fa460c4332
Placements moved only on full-screen scrolls, so a program with a fixed
header and footer (DECSTBM) scrolled its text under or over a stationary
image, and a reverse scroll left images behind as well. Kitty moves each
placement that lies wholly inside the scrolled band, clips one that the
move takes partly past a margin, and drops one taken wholly outside.

The grid now queues a RegionScrolled effect for every band scroll that
is not one of the two full-screen cases, coalescing adjacent scrolls of
one band. The daemon runs kitty's scroll_filter_margins_func over the
placement table: the clipped rows come off the extent (and a requested
r=) and off the source rectangle at the scale the image draws at, as
src_pixels_per_screen_pixel computes it, so what is left keeps its size
and place. Survivors are re-sent as Placement because a uniform
PlacementsShifted cannot express a partial move or a clip; no wire
change is needed.

Two cases depart from kitty where its region path disagrees with its
full-screen one. A region anchored at the primary top carries its
placements into history whole, together with those already there, as a
full-screen scroll does; kitty clips them and leaves history placements
behind, off their text. A full-screen reverse scroll uses the region
rule; kitty moves history placements down although no history text
moves, and keeps those pushed off the bottom.

Assisted-by: Claude Code
grid: state the rounding bound of coalesced region scrolls
All checks were successful
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 9s
darwin / darwin docs-only no-op (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 16s
windows / windows docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m12s
bench / instruction-count regression gate (pull_request) Successful in 1m2s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 45s
pr / nix flake check (pull_request) Successful in 39s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m48s
pr / wire schema is compatible with the base (pull_request) Successful in 3s
windows / cargo nextest (Windows) (pull_request) Successful in 4m19s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 53s
windows / cargo clippy (Windows cross) (pull_request) Successful in 15s
windows / frontend smoke (Windows) (pull_request) Successful in 46s
f0ac3750cc
The coalescing comment claimed exact equivalence; source cuts round to
whole pixels per clip, so split and merged scrolls can differ by a
pixel of source. The bound is what callers may rely on.

Assisted-by: Claude Code
Sign in to join this conversation.
No reviewers
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
felis-terminal/felis!126
No description provided.