Files
ghostty-rs/docs/rewrite/notes/ghostty-cli.md
T
ddidderr f2c4b40062 feat(cli): port ghostty +action CLI commands
Ports ZIG/src/cli into ghostty-cli: action detection/dispatch with
per-action --help, and the actions help, version, list-actions,
list-colors, list-keybinds, list-themes (plain listing plus an
interactive ratatui browser mirroring Ghostty's libvaxis UI),
show-config, validate-config, edit-config, crash-report (listing),
ssh-cache and boo (235 embedded frames).

Verified byte-for-byte against the installed Ghostty 1.3.1 for stdout,
stderr and exit codes of most actions, incl. tty output in a pty at
widths 20-200. Known differences: +list-colors --plain prints all 782
colors (Ghostty truncates at 617 because it never flushes), +version
has placeholder build info until build_info::set is wired up.

Deferred: list-fonts, show-face (need font discovery integration) and
new-window (needs GTK IPC); they return NotYetAvailable for now.

Test Plan:
- cargo test -p ghostty-cli (86 tests)
- cargo run -p ghostty-cli --example cli -- +list-keybinds --plain
  and diff against `ghostty +list-keybinds --plain`
2026-10-11 22:22:01 +02:00

6.0 KiB
Raw Blame History

ghostty-cli port notes (C1)

Crate crates/ghostty-cli (deps: ghostty-config, ghostty-input, ghostty-os; plus ratatui/crossterm for TUIs, flate2 for boo frames, unicode-width). Run end to end with cargo run -p ghostty-cli --example cli -- +list-actions --docs (the example behaves like ghostty for actions via ghostty_cli::run_from_args, which also reproduces main_ghostty.zig's "multiple/unknown action" errors). Do not edit the root crate: wire run_from_args(&std::env::args().collect::<Vec<_>>()) into its main later.

API

Every action module exposes pub fn run(args: &[String]) -> Result<u8, Error>; args is the full argv (argv[0] skipped, +action args ignored, like args.argsIterator). --help/-h yields Error::ActionHelpRequested, which Action::run turns into the help text (stdout, exit 0). Action::{ALL,name,from_name,file,help_text,run}, action::{detect_iter,ActionEnum,SpecialCase}.

File -> module

Zig Rust
cli.zig / action.zig action.rs (generic detection + 7 tests)
ghostty.zig ghostty.rs (Action, dispatch, help, run_from_args; 5 tests)
help.zig, version.zig help.rs, version.rs (+ build_info.rs)
list_actions/list_colors/list_keybinds same names; pretty output via tui.rs
list_themes.zig list_themes.rs (listing) + list_themes/preview.rs (ratatui TUI)
show_config/validate_config/edit_config/crash_report same names
ssh_cache.zig + ssh-cache/{Entry,DiskCache} ssh_cache.rs, ssh_cache/{entry,disk_cache}.rs
boo.zig (+ build/framegen/frames) boo.rs, boo_frames.deflate (235 frames, raw deflate of frames joined by \x01)
tui.zig tui.rs (+ a small vaxis prettyPrint clone)
list_fonts/show_face/new_window stubs: Options + help + Error::NotYetAvailable (TODO(integration))

Help texts (help/*.txt) are the run doc comments of the Zig files, extracted with the helpgen rules (common-indent strip); --help output is byte-identical to Ghostty 1.3.1.

Oracle verification (Ghostty 1.3.1 at /usr/bin/ghostty)

Byte-identical (stdout, stderr, exit code): +list-actions (+--docs, --help), +list-keybinds (--plain, --default, --docs; also with a custom config containing chords, chains, tables, catch_all, unicode keys), +list-themes --plain (--path, --color=dark|light; with GHOSTTY_RESOURCES_DIR=/usr/share/ghostty), +help, --help, -h, +validate-config (ok and missing file), +crash-report, +ssh-cache, bad action / multiple actions / bad flag errors, --help of every action, +show-config (except the two known config deviations: auto-update-channel tip vs stable, gtk-opengl-debug debug build). Pretty (tty) output of +list-colors and +list-keybinds is byte-identical in a pty for widths 20..200 (including line wrapping and the first-row/teardown sequences of libvaxis); verified with a pty harness.

Known differences:

  • +list-colors --plain: Ghostty 1.3.1 never flushes its 4 KiB stdout buffer on this path, so its output is truncated at 617 of 782 lines. We print all 782 (first 617 identical).
  • +list-colors in a tty narrower than one column: Zig divides by zero; we clamp to one column.
  • +version differs in version/build info (placeholders in build_info; TODO(integration): root crate / apprt-gtk should call build_info::set with the real GTK/libadwaita versions, commit and channel). "Zig version" row became "Rust version", "libxev" row prints n/a.
  • +edit-config exec failure prints error.FileNotFound/error.AccessDenied for those kinds, otherwise the Rust io error text.
  • +new-window: the Zig parse hook swallows --help; ported as is. IPC not available.

Interactive +list-themes (ratatui)

Same layout as the vaxis version: 32-column list with ❯ name ❮ selection, preview (header, 16 color swatches with decimal/hex numbers, 24-line code sample, lorem ipsum filler, diagnostics / load-error panels), help (60x22), search (3-high box at bottom) and save (92x17) overlays, all key bindings (q/Esc/^C, ?/F1/^H, /, ^X ^/, j/k/arrows, PgUp/PgDn (20), Home/End, h/x/d hex toggle, c / C copy name/path via OSC 52, f cycle filter, Enter save dialog, w write auto/theme.ghostty), mouse wheel / click / hover. Differences: no OSC 8 hyperlinks on names, double/dashed/dotted/curly underlines drawn as single, color scheme not queried live (defaults to light like Zig; COLORFGBG used as a hint), theme load errors during filtering are ignored (Zig aborts), zf fuzzy ranking replaced by a subsequence matcher (only matching, not order, is used). Logic (filtering, navigation, windowing, search, save) is unit tested; rendering is tested against a ratatui Buffer.

Tests

Ported every Zig test in the ported files: action.zig (7 detect tests), ghostty.zig (5 parse action tests), ssh-cache/Entry.zig (6), DiskCache.zig (4 operation tests + isValidHost, isValidUser, isValidCacheKey). Skipped: none. 86 tests total including new ones (help/version output, show-config, validate-config, crash-report, ssh-cache action, list-colors/keybinds/themes, tui row rendering vs vaxis bytes, boo frame parsing of all 235 frames, preview state machine, new-window arg hook).

TODO(integration) / gaps

  • list-fonts, show-face (need ghostty-font), new-window (GTK IPC): return Error::NotYetAvailable.
  • build_info::set for real version/GTK info.
  • ghostty-vt x11_color lacks a key listing and loses name case: list_colors re-parses ghostty-vt/src/res/rgb.txt through a relative include_str! path.
  • ghostty-input ActionKey::docs() has an extra trailing blank line for 4 actions (adjust_selection, ...) compared to Ghostty's help strings; list_actions::generate_plaintext is a local copy of helpgen_actions::generate that normalises trailing newlines. Fix the data in ghostty-input and switch back to helpgen_actions::generate.
  • crash-report only lists (sentry is not ported). ssh-cache locking uses File::try_lock; like Zig, a lock held by another process is ignored (the boolean result is unused upstream).
  • Config::load and finalize are called twice in validate-config (as in Zig).