refactor(terminal): wire kitty graphics and drop PageList workarounds

Integration pass over the terminal core (Codex/gpt-6-astra, reviewed):
- Screen now owns the real kitty ImageStorage; new/reset/teardown,
  erase-display deletes and alternate-screen limit inheritance hook in
  where Zig does, and Terminal implements ExecTerminal so kitty APC
  commands execute against it. Exec tests use a real Terminal.
- The PageList gains the APIs Screen/Terminal needed (pin iterators,
  cross-page row/partial-row clone, string allocator probing, dedup
  before allocating hyperlinks, Top viewport fixup after erase, append_
  grapheme no longer marking rows dirty), and the Screen/Terminal
  workarounds for their absence are removed so the code mirrors Zig.

Public signatures are unchanged; downstream crates build as before.

Test Plan:
- cargo test -p ghostty-terminal (1252 passed, 0 ignored)
- cargo clippy -p ghostty-terminal --all-targets -- -D warnings
This commit is contained in:
ddidderr committed 2026-10-11 22:57:35 +02:00
1 parent 69d823cbc7
commit 08452ac84f
12 files changed
+708 -476

No files matched your search

+23 -6
View File
@@ -3,13 +3,13 @@
## Ported
| Zig | Rust | tests |
|---|---|---|
| Screen.zig | `screen.rs` (+ `cursor.rs` for cursor.zig `Style`, `pin_iter.rs` helper) | 183 of 184 (`screen/tests/p1..p5.rs`) |
| Screen.zig | `screen.rs` (+ `cursor.rs` for cursor.zig `Style`) | 183 of 184 (`screen/tests/p1..p5.rs`) |
| ScreenSet.zig | `screen_set.rs` | 1/1 |
| Tabstops.zig | `tabstops.rs` | 4/5 |
| Selection.zig | `selection.rs` | 18/18 |
Skipped: `Tabstops: resize alloc failure preserves state` and `selectionString map allocation failure cleanup` (Zig tripwire failure injection).
Ignored (pagelist limitations, see `ghostty-terminal-pagelist-requests.md`): 3 resize tests.
All three formerly blocked resize tests are enabled and pass. No ignored tests.
Deps added: ghostty-unicode (width in `test_write_string`), ghostty-datastruct (unused so far), log.
## Deviations
@@ -19,11 +19,11 @@ Deps added: ghostty-unicode (width in `test_write_string`), ghostty-datastruct (
- `clone` is `clone_screen(top, bot)`. `cursor_copy(&Cursor, hyperlink: bool)` copies the hyperlink from the other cursor's heap copy.
- `append_grapheme(x, cp)` takes a column of the cursor row. `clear_cells(node, y, x0, x1)` takes a half-open column range.
- Selection methods take `&PageList` (`&screen.pages`) since tracked bounds are handles: `sel.start(&pages)`, `top_left`, `order`, `contains`, `contained_row`, `ordered`, `eql(&pages, &other)`, `adjust(&mut pages, adj)`, `track(&mut pages)`, `deinit(&mut pages)`. `Selection` is `Copy`.
- `kitty_images` is a placeholder `KittyImages { dirty }` (TODO(integration) until kitty graphics storage lands). `StringMap` is a local struct (TODO(integration)).
- Minimal plain-text formatter (`plain_format` in screen.rs) backs `selection_string[_map]`, `dump_string`, `dump_string_alloc[_unwrapped]`; TODO(integration): delegate to formatter.rs.
- `kitty_images` owns `kitty::graphics_storage::ImageStorage`: initialization applies the configured limit; reset releases placement pins and restores default storage with `dirty = true`; Drop releases storage before pages. `clone_screen` does not clone images.
- `StringMap` re-exports the real `string_map::StringMap`.
- Selection/dump strings use the real `formatter::ScreenFormatter`.
- Tabstops::unset XORs like Zig (unsetting an unset stop sets it).
- `cursor_set_hyperlink` does not probe the string allocator (private); it re-adds the link after growth.
- `Screen::erase_rows` carries a `Viewport::Top` workaround (request 5).
- `cursor_set_hyperlink` follows the reference allocator probe and capacity-growth order. PageList owns viewport repair and cross-page row copying; Screen uses native Pin iterators and exact cursor restoration after page splitting. No PageList workarounds remain.
## API overview for Terminal
`Screen` fields: `pages, no_scrollback, cursor, saved_cursor, selection, charset, protected_mode, kitty_keyboard, kitty_images, semantic_prompt, dirty`.
@@ -32,3 +32,20 @@ Editing: `clear_cells`, `clear_unprotected_cells`, `clear_rows`, `erase_rows`, `
Style/links: `set_attribute(sgr::Attribute)`, `manual_style_update()`, `start_hyperlink(uri, id)`, `end_hyperlink()`, `cursor_set_hyperlink()`, `cursor_set_semantic_content(..)`.
Selection: `select(Option<Selection>)`, `clear_selection()`, `select_line/all/word/word_between/output`, `line_iterator`, `prompt_click_move`, `selection_string`.
Test helper: `test_write_string`.
## API changes (integration pass)
`Screen.kitty_images` now exposes the required real `ImageStorage`; its existing
`dirty` field access remains valid. The old standalone `KittyImages` value type
is retained for compatibility, but is no longer used by Screen. Other public
signatures, including `Screen::reset() -> Result`, are unchanged. No renderer,
font, or termio callers needed edits.
## Integration verification (2026-10-11)
All Screen/Terminal TODO and Zig-reference comments were reviewed against the
reference. Remaining TODOs also occur in Zig (capacity-growth recovery and a
selection test note); no integration workaround remains. Lifecycle regressions
cover real image storage, clone isolation, reset, and placement-pin release.
Crate gate: 1252 unit + 1 documentation tests pass, none ignored; clippy with
warnings denied, rustfmt, and the combined font/renderer/termio build pass.