Keep a multi-row text-sizing character whole or erase it #33

Merged
natsukium merged 1 commit from fix/multirow-block-shift into main 2026-10-06 20:39:51 +09:00
Owner

Closes #24.

A text-sizing (OSC 66) character taller than one row is drawn from its primary over its whole footprint. Any cell move that carried some of its rows and not the others tore it: ICH or insert mode on one of its rows, DECIC/SR/SU in a region whose margin crossed it, IL/DL through it. The primary then drew over other text, or rows were left with no primary.

Before each move, a block that a row seam of the move splits is now erased whole, as kitty's nuke_multiline_char_intersecting_with does. A scroll into scrollback keeps the departing rows in history and clears only the rows left on screen.

A new property test over random moves and OSC 66 writes found three more ways to leave part of a block behind, fixed here too:

  • an OSC 66 write over another block's lower rows erased only the cells it covered;
  • SL/SR, DECIC/DECDC and DECBI/DECFI cut column seams row by row after earlier rows had moved;
  • insert mode left a copy of the shifted cell under the cursor, which erased the moved sized character.

Tests: a table test whose every cut case fails on main; scrollback and alternate-screen cases; the property test (passed 4×3000 cases). Docs: reference and explanation of kitty-text-sizing.md, CHANGELOG.

Closes #24. A text-sizing (OSC 66) character taller than one row is drawn from its primary over its whole footprint. Any cell move that carried some of its rows and not the others tore it: ICH or insert mode on one of its rows, DECIC/SR/SU in a region whose margin crossed it, IL/DL through it. The primary then drew over other text, or rows were left with no primary. Before each move, a block that a row seam of the move splits is now erased whole, as kitty's `nuke_multiline_char_intersecting_with` does. A scroll into scrollback keeps the departing rows in history and clears only the rows left on screen. A new property test over random moves and OSC 66 writes found three more ways to leave part of a block behind, fixed here too: - an OSC 66 write over another block's lower rows erased only the cells it covered; - SL/SR, DECIC/DECDC and DECBI/DECFI cut column seams row by row after earlier rows had moved; - insert mode left a copy of the shifted cell under the cursor, which erased the moved sized character. Tests: a table test whose every cut case fails on main; scrollback and alternate-screen cases; the property test (passed 4×3000 cases). Docs: reference and explanation of `kitty-text-sizing.md`, CHANGELOG.
grid: keep a multi-row OSC 66 block whole or erase it
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
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
windows / detect relevant changes (pull_request) Successful in 29s
pr / nix flake check (pull_request) Successful in 38s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m24s
pr / wire schema is compatible with the base (pull_request) Successful in 2s
windows / cargo clippy (Windows cross) (pull_request) Successful in 23s
windows / cargo nextest (Windows) (pull_request) Successful in 6m57s
bench / Criterion regression gate (pull_request) Has been cancelled
windows / windows docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m4s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m12s
pr / frontend smoke (x86_64-linux) (pull_request) Failing after 36s
windows / frontend smoke (Windows) (pull_request) Has been cancelled
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
b290141b4f
A text-sizing block taller than one row is drawn from its primary over
its whole footprint. Any move that carried some of its rows and not the
others tore it: ICH on its top row shifted that row only, and DECIC, SR
or SU in a region whose margin crossed it, or IL and DL through it, did
the same between rows. The primary then drew over cells that held other
text, or rows were left with no primary at all. The earlier fix covered
a move's column boundaries only.

Before each move, a block that a row seam of the move splits is now
erased, as kitty's nuke_multiline_char_intersecting_with does. The seams
are the cursor row for a row-local shift, the region's top and bottom
for a column shift, and those plus the boundary between departing and
staying rows for a vertical scroll. A scroll into scrollback keeps the
departing rows of the block in history and clears only the rows left on
screen, so the character's text survives; when both the history seam and
the bottom seam cut one block, history wins.

A property test over random moves and OSC 66 writes found three more
ways to leave part of a block behind, fixed here too:

- An OSC 66 write cleared foreign sized cells only on its top row; its
  lower rows now erase a block they land on whole.
- SL/SR, DECIC/DECDC and DECBI/DECFI cut each row's column seams after
  the rows above had moved, so a moved row straddled the next row's seam
  and erased the block's remaining cells only there. All seams are now
  cut before any row moves.
- IRM left the old cell under the cursor after shifting the row; read as
  a sized primary it erased the moved original. The vacated span is now
  blanked, as ICH does.

The seam scan is gated on has_sized_cells and reads each seam row as one
slice: a scroll benchmark shows no change without sized text and a few
percent once an OSC 66 write has set the flag.

Closes #24

Assisted-by: Claude Code
Author
Owner

The Criterion gate flagged daemon_side/shell_prompt (+15.6%) and multi_cycle_end_to_end/plaintext_scroll_individual (+29.9%). A local A/B against main (separate target dirs, base and head alternated, three rounds) shows no regression:

bench main (3 runs) this branch (3 runs)
daemon_side/shell_prompt 36.1 / 35.4 / 35.4 µs 34.4 / 35.3 / 34.7 µs
multi_cycle_end_to_end/plaintext_scroll_individual 1.25 / 2.31 / 2.54 ms 1.15 / 1.21 / 1.44 ms
multi_cycle_end_to_end/plaintext_scroll_batched 1.44 / 2.44 / 1.12 ms 1.39 / 1.25 / 1.57 ms

The multi-cycle benches are bimodal on this host (1.1–2.5 ms on main alone); the seam scan returns before any work when no OSC 66 cell has been written.

The Criterion gate flagged `daemon_side/shell_prompt` (+15.6%) and `multi_cycle_end_to_end/plaintext_scroll_individual` (+29.9%). A local A/B against main (separate target dirs, base and head alternated, three rounds) shows no regression: | bench | main (3 runs) | this branch (3 runs) | | --- | --- | --- | | `daemon_side/shell_prompt` | 36.1 / 35.4 / 35.4 µs | 34.4 / 35.3 / 34.7 µs | | `multi_cycle_end_to_end/plaintext_scroll_individual` | 1.25 / 2.31 / 2.54 ms | 1.15 / 1.21 / 1.44 ms | | `multi_cycle_end_to_end/plaintext_scroll_batched` | 1.44 / 2.44 / 1.12 ms | 1.39 / 1.25 / 1.57 ms | The multi-cycle benches are bimodal on this host (1.1–2.5 ms on main alone); the seam scan returns before any work when no OSC 66 cell has been written.
natsukium force-pushed fix/multirow-block-shift from b290141b4f
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
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
windows / detect relevant changes (pull_request) Successful in 29s
pr / nix flake check (pull_request) Successful in 38s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m24s
pr / wire schema is compatible with the base (pull_request) Successful in 2s
windows / cargo clippy (Windows cross) (pull_request) Successful in 23s
windows / cargo nextest (Windows) (pull_request) Successful in 6m57s
bench / Criterion regression gate (pull_request) Has been cancelled
windows / windows docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m4s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m12s
pr / frontend smoke (x86_64-linux) (pull_request) Failing after 36s
windows / frontend smoke (Windows) (pull_request) Has been cancelled
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
to 0dbe23dab7
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
windows / windows docs-only no-op (pull_request) Has been skipped
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 1m0s
windows / detect relevant changes (pull_request) Successful in 13s
bench / Criterion regression gate (pull_request) Failing after 1m46s
pr / cargo build / clippy / test / deny (pull_request) Has been cancelled
windows / cargo nextest (Windows) (pull_request) Has been cancelled
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m33s
pr / nix flake check (pull_request) Successful in 11s
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
windows / frontend smoke (Windows) (pull_request) Has been cancelled
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
2026-10-06 19:46:08 +09:00
Compare
natsukium force-pushed fix/multirow-block-shift from 0dbe23dab7
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
windows / windows docs-only no-op (pull_request) Has been skipped
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 1m0s
windows / detect relevant changes (pull_request) Successful in 13s
bench / Criterion regression gate (pull_request) Failing after 1m46s
pr / cargo build / clippy / test / deny (pull_request) Has been cancelled
windows / cargo nextest (Windows) (pull_request) Has been cancelled
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m33s
pr / nix flake check (pull_request) Successful in 11s
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
windows / frontend smoke (Windows) (pull_request) Has been cancelled
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
to d0584f663f
Some checks failed
darwin / detect relevant changes (pull_request) Successful in 8s
darwin / darwin docs-only no-op (pull_request) Has been skipped
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 10s
windows / windows docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m2s
pr / nix flake check (pull_request) Successful in 37s
windows / cargo nextest (Windows) (pull_request) Failing after 11m42s
windows / frontend smoke (Windows) (pull_request) Has been cancelled
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
bench / Criterion regression gate (pull_request) Successful in 2m15s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m13s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m19s
pr / wire schema is compatible with the base (pull_request) Successful in 2s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 42s
windows / cargo clippy (Windows cross) (pull_request) Successful in 15s
2026-10-06 19:53:09 +09:00
Compare
natsukium force-pushed fix/multirow-block-shift from d0584f663f
Some checks failed
darwin / detect relevant changes (pull_request) Successful in 8s
darwin / darwin docs-only no-op (pull_request) Has been skipped
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 10s
windows / windows docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 1m2s
pr / nix flake check (pull_request) Successful in 37s
windows / cargo nextest (Windows) (pull_request) Failing after 11m42s
windows / frontend smoke (Windows) (pull_request) Has been cancelled
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
bench / Criterion regression gate (pull_request) Successful in 2m15s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m13s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m19s
pr / wire schema is compatible with the base (pull_request) Successful in 2s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 42s
windows / cargo clippy (Windows cross) (pull_request) Successful in 15s
to dfb85a51a9
All checks were successful
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 20s
windows / windows docs-only no-op (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Successful in 1m50s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m12s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m24s
pr / wire schema is compatible with the base (pull_request) Successful in 2s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 27s
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 1m1s
pr / nix flake check (pull_request) Successful in 9s
windows / cargo clippy (Windows cross) (pull_request) Successful in 14s
windows / cargo nextest (Windows) (pull_request) Successful in 23m24s
windows / frontend smoke (Windows) (pull_request) Successful in 2m19s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
2026-10-06 20:12:46 +09:00
Compare
natsukium deleted branch fix/multirow-block-shift 2026-10-06 20:39:51 +09:00
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!33
No description provided.