Skip to content

Add serde-based terminal state snapshot/restore - #3

Open
dsturnbull wants to merge 6 commits into
zed-industries:masterfrom
dsturnbull:pty-host-serde
Open

Add serde-based terminal state snapshot/restore#3
dsturnbull wants to merge 6 commits into
zed-industries:masterfrom
dsturnbull:pty-host-serde

Conversation

@dsturnbull

Copy link
Copy Markdown

Add serde-based terminal state snapshot/restore

Adds Term::snapshot() and Term::restore() for capturing and restoring terminal state via any serde-compatible format. This enables session persistence across PTY reconnects (used by pty-host) without coupling to a specific serialisation format.

What changed

alacritty_terminal/src/term/serialize.rs (new)

Introduces TermState — a serde-derived struct capturing the subset of Term state needed for restore:

Field Purpose
grid Active grid with cursor, scrollback, cell content & attributes
inactive_grid Inactive buffer (covers alternate screen)
mode_bits TermMode bitflags (bracketed paste, mouse mode, alt screen, …)
scroll_region DECSTBM scroll region

Not captured: tab stops, charsets, window title, keyboard mode stack, cursor style, selection, vi mode cursor — these are either reconstructed from defaults or owned by the client (full list).

alacritty_terminal/src/term/mod.rs

Term::snapshot() and Term::restore() added as methods on Term, following the existing convention where all state-mutating operations live directly on the type. Both are gated behind #[cfg(feature = "serde")].

alacritty_terminal/src/grid/mod.rs

  • Cursor<T> gains Serialize/Deserialize derives.
  • cursor and saved_cursor on Grid flip from #[serde(skip)] to #[serde(default)] so they survive round-trips.
  • charsets is marked #[serde(skip)] because vte::ansi::StandardCharset lacks serde derives upstream (see known limitation below).

alacritty_terminal/Cargo.toml

bincode added as a dev-dependency for binary round-trip tests. serde_json was already present.

Known limitation

Cursor.charsets is skipped during serialisation because StandardCharset and CharsetIndex in the vte crate don't derive Serialize/Deserialize. A fix adding the two derives is ready in a local vte branch — once merged upstream, charsets can flip from skip to default.

Testing

11 tests in serialize.rs:

  • Grid round-trip, cursor survival, WRAPLINE preservation, scrollback
  • snapshotrestore preserves: content + cursor, mode flags, alternate screen, scroll region, scrollback
  • Format-specific round-trips: JSON, bincode

David Turnbull added 6 commits March 3, 2026 16:35
Add Term::snapshot() and Term::restore() for capturing and restoring
full terminal state (both grid buffers, cursor, mode flags, scroll
region) via any serde-compatible format.

Includes TermState struct in the serialize module with round-trip tests
for JSON and bincode. Grid, Storage, Row, Cell, and related types gain
Serialize/Deserialize derives behind the existing serde feature flag.
Compress old scrollback rows to reduce terminal memory usage.
Rows are trimmed of trailing default cells, serialized with bincode,
and compressed with zstd, achieving ~28x compression on typical
mixed terminal content.

Grid<Cell> gains:
- compress_old_scrollback(keep_hot) / thaw_compressed_history(count)
- compact_scrollback_if_needed() for automatic compression
- scroll_display_with_thaw() for transparent decompression on scroll

192 tests passing including 40+ new compression tests covering
round-trip fidelity, compression ratios, freeze/thaw integration,
and automatic compress/thaw triggers.
Mirrors Term::scroll_display but delegates to
Grid::scroll_display_with_thaw, which automatically decompresses
compressed scrollback rows when the scroll target reaches into
compressed history.
No-op hot path: 10k calls in <5ms (single integer comparison).
Compression: 11.2 µs/row (release), 269 µs per screenful.
Decompression: 5.3 µs/row (release), 127 µs per screenful.

The decompression latency assertion only fires in release builds
to avoid false positives from debug-mode overhead.
compact_scrollback_if_needed was unconditionally compressing all hot
history beyond 2x screen lines, ignoring display_offset. The subsequent
clamp in compress_old_scrollback would yank the viewport back, making it
impossible to scroll more than ~2 screens into compressed history.

Account for display_offset when choosing how many rows to keep hot.
Search methods now take &mut self so regex_search_internal can thaw
compressed history before iterating. This makes search find content
that has been compressed into scrollback.

Also adds Grid::total_topmost_line() for callers that want the search
range to include compressed rows.
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.

1 participant