feat: port GTK version fallbacks, helpgen, bench modes, CLI leftovers

- apprt-gtk: Ghostty's runtime+build-time version gating (at_least)
  with cargo features adw_1_3/adw_1_4/adw_1_5/gtk_4_16 (default on) and
  the fallbacks for older libraries: MessageDialog (adw < 1.5),
  clipboard dialog without SwitchRow, pre-Banner debug warning and
  child-exited widgets, the old about dialog, GTK 4.16 CSS error source
  bytes. The minimum stays GTK 4.14 / libadwaita 1.5 (Ghostty's floor).
  The root build links with -z lazy so version-gated symbols don't keep
  older libraries from loading.
- ghostty-helpgen (port of helpgen.zig) generates CLI action help and
  keybinding action docs from doc comments at build time, replacing the
  checked-in help/*.txt; +help and +list-actions --docs stay
  byte-identical to Ghostty 1.3.1.
- ghostty-bench: libc wcwidth mode and separate simd/table and
  uucode/table code paths like Zig.
- ghostty-cli: Windows branches of edit-config, list-colors,
  list-keybinds and boo; ssh-cache DiskCache flock locking (no-op on
  Windows, as in Zig) with an exclusivity test.

Test Plan:
- cargo test --workspace
- diff <(cargo run -q -- +list-actions --docs) \
  <(ghostty +list-actions --docs)
This commit is contained in:
ddidderr committed 2026-10-12 06:38:33 +02:00
1 parent cd86fe92a8
commit f2252bb165
51 files changed
+2426 -1621

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
# ghostty-bench: CodepointWidth / IsSymbol (compat-GC)
- `codepoint-width` modes (CLI as Zig: noop|wcwidth|simd|table):
- `wcwidth`: real libc `wcwidth` via a local `extern "C"` (no libc crate), in
a module with `#[allow(unsafe_code)]` + SAFETY comment. Zig does not call
`setlocale`, so neither do we (C locale). Non-unix: warning no-op like Zig on Windows.
- `simd`: new `src/simd_width.rs`, scalar port of `simd/codepoint_width.cpp`
(same range tables and precedence; vector lanes replaced by a sequential scan).
- `table`: generated two-stage table with the `<= 0xFF => 1` shortcut.
- `is-symbol` modes: `uucode` (default) = `is_symbol_computed`, computed from
private-use + block ranges (`computeIsSymbol`); `table` = generated symbols table.
Test verifies equality with the table over all codepoints.
- Cargo.toml: `[lints]` no longer `workspace = true` (workspace `forbid` cannot be
locally allowed); `unsafe_code = "deny"` and the clippy set is copied. Keep in sync.
- Deviation: Zig's `simd` falls back to uucode when built without SIMD; there is no
separate uucode width mode in the CLI (none in Zig), so none added.
- Tests: all modes run on a sample file; computed vs table symbols; simd vs table
samples; wcwidth ASCII. Zig `test CodepointWidth/IsSymbol` (run once, no data) covered by the run tests.
+13
View File
@@ -0,0 +1,13 @@
# compat-GC: ghostty-cli Windows branches and DiskCache locking
Ported (all `cfg(windows)` / cfg-gated, no unsafe, no new deps):
* `edit_config.rs`: Windows prints "not supported on Windows ... edit manually at <path>" to stderr and returns 1 (after the path lookup, before the editor lookup, as in Zig). The `exec` path is now `cfg(unix)` (`exec_editor`).
* `list_colors.rs` / `list_keybinds.rs`: both use `tui::window_size()`; on Windows it returns the fixed Zig default (120 cols x 24 rows) instead of querying the tty. Pixel size (1024x768) is not modelled (unused).
* `boo.rs`: Zig allows windows/macos/linux/freebsd and returns 1 elsewhere; mirrored with a `cfg!` check. Windows needs no other change.
* `ssh_cache/disk_cache.rs`: made the module compile on Windows (unix-only `mode`/`PermissionsExt` gated; `fixup_permissions` is a no-op on Windows as in Zig "Windows does not support chmod").
Locking deviation from the task brief: Zig does NOT lock on Windows (`if (comptime builtin.os.tag != .windows) file.tryLock(.exclusive)` and the matching unlock). So the faithful port is a no-op lock on Windows rather than LockFileEx; hence no windows-sys/unsafe. On unix the existing implementation already matched Zig: `File::try_lock` (flock LOCK_EX|LOCK_NB), WouldBlock is ignored (proceeds unlocked, as Zig ignores the bool), other errors -> CacheIsLocked, unlock on drop.
Tests: unix `lock_is_exclusive_until_dropped`; windows-only `lock_is_noop_on_windows` (compiled only); permission assertion split into unix-only `disk_cache_file_permissions`.
Verified: build/test(92)/clippy -D warnings on linux; `cargo check --target x86_64-pc-windows-gnu --tests -p ghostty-cli` and windows clippy clean (stub fontconfig.pc, RUSTC_WRAPPER= as in windows-WIN.md).
+43
View File
@@ -0,0 +1,43 @@
# compat-GC: helpgen (ZIG/src/helpgen.zig)
Zig generates help strings at build time from doc comments (`helpgen.zig`,
exposed as `help_strings`). The Rust port does the same instead of keeping
checked-in tables.
## Design
* New crate `crates/ghostty-helpgen` (no dependencies), used as a
`[build-dependencies]` of `ghostty-cli` and `ghostty-input`.
* `doc_lines`: port of `extractDocComments`/`findCommonPrefix` (strip
`///`, remove the common leading-space prefix of non-blank lines, keep
blank lines and code fences verbatim).
* `run_fn_doc`: doc comment of a module's `run` function (Zig `genActions`).
* `enum_variant_docs`: `///` docs per variant of an enum (Zig
`genKeybindActions`).
* `*_match`: emit Rust `match self { ... }` bodies with escaped literals.
* `ghostty-cli/build.rs` reads the variants of `enum Action` in
`src/ghostty.rs`, maps `ListFonts` -> `src/list_fonts.rs`, extracts the doc
of `pub fn run`, writes `$OUT_DIR/action_help.rs`; `Action::help_text`
`include!`s it. A missing doc comment fails the build (as in Zig).
The former `src/help/*.txt` files are deleted.
* `ghostty-input/build.rs` extracts the docs of every variant of
`enum Action` in `src/binding/action.rs` (now `///` instead of `//`),
writes `$OUT_DIR/keybind_action_docs.rs`, included by `ActionKey::docs`
(docs = lines joined by `\n` plus one trailing `\n`, as before;
`help_string` strips one).
* Config help already derives from field docs through `config_struct!`
(`ghostty-config/src/macros.rs`), so no generation was needed there.
* A line-based scanner is used rather than `syn`: it mirrors Zig's token
based approach (doc run + following item) and avoids a heavy build dep.
## Verification
Outputs of `+help`, `+list-actions [--docs]` and every `+<action> --help`
were captured before and after: byte identical. Differences vs
`/usr/bin/ghostty` exist only for `+version --help` and `+new-window --help`
(unchanged from the baseline).
## Tests
`ghostty-helpgen` unit tests (marker stripping, common prefix, blank lines,
code fences, `////`, run-doc and enum-variant extraction, escaping).
+76
View File
@@ -0,0 +1,76 @@
# compat-GC: GTK/libadwaita compat fallbacks, bench modes, helpgen, CLI Windows
Sub-notes: `compat-GC-bench.md`, `compat-GC-helpgen.md`, `compat-GC-cli.md`.
## 1. GTK / libadwaita compatibility (crates/ghostty-apprt-gtk)
### Ghostty's own minimum (finding)
`ZIG/src/build/SharedDeps.zig` runs `blueprint.zig` over *every* entry of
`gresource.blueprints`, which refuses to compile a `<maj>.<min>/x.blp` if the
libadwaita headers are older than that version; the list contains `1.5/` files
(window, tab, command palette, ...). So Ghostty 1.3.1 cannot be *built* against
libadwaita < 1.5, and the Zig code calls `gtk_css_provider_load_from_bytes`
(4.12) and `gtk_im_context_activate_osk` (4.14) unguarded. Ghostty's real
minimum is therefore **GTK 4.14 / libadwaita 1.5**, which is what the crate
already required; I verified this by compiling with `gtk4` features `v4_8`/`v4_10`
(fails on `load_from_bytes`/`activate_osk`) and `v4_14` (compiles). The only
change to the requirement is dropping the unneeded `gnome_44` feature. In Zig the
fallbacks below are only reachable in a (hypothetical) build against older
headers; they are ported with identical selection logic.
### Version gating (gtk_version.rs, adw_version.rs)
Zig's `atLeast` = comptime header version AND runtime library version. Rust:
* cargo features are the "header version": `adw_1_3` < `adw_1_4` < `adw_1_5`
and `gtk_4_16` (`gtk/v4_16`); all are default. `BUILD_LEVEL` /
`const fn comptime_at_least` read them; `at_least` = `comptime_at_least &&
runtime_at_least`; `runtime_at_least`/`runtime_until` unchanged.
`COMPTIME_VERSION` (pkg-config) is kept for `+version` output.
* Types that Zig selects at comptime are selected with `cfg(feature = ...)`.
* Debug builds only: `GHOSTTY_DEBUG_FAKE_GTK_VERSION` / `GHOSTTY_DEBUG_FAKE_ADW_VERSION`
(e.g. `1.4.0`) override the *runtime* version answers (`fake_version.rs`; no Zig
counterpart) so runtime branches can be exercised on new libraries.
* Root `build.rs` links the binary with `-z lazy` (Rust defaults to `-z now`) so
symbols only used behind a runtime check (GTK 4.16 `gtk_css_section_get_bytes`)
don't stop an older library from loading the program, like Zig's lazy binding.
### Fallbacks ported
| Zig | Rust |
|---|---|
| `class/dialog.zig` `Parent = AlertDialog / MessageDialog`, `present`/`close` | `class/dialog.rs`: `DialogParent` + `present`/`close` per cfg `adw_1_5`; macros `dialog_subclass_wrapper!`/`impl_dialog_parent!` give the three subclasses (close-confirmation, config-errors, clipboard) the right `@extends` chain and `response` override for both parents |
| `clipboard_confirmation_dialog.zig` `can_remember`, template 1.4 vs 1.0 | `cfg_attr` template resource `ui/1.4` (adw_1_4) vs `ui/1.0`; `remember_choice` child only with SwitchRow; `CAN_REMEMBER`; `remember=false` without it |
| `debug_warning.zig` Parent `adw.Bin`/`gtk.Box`, template 1.3/1.2 | `class/debug_warning.rs` cfg `adw_1_3` |
| `surface_child_exited.zig` Banner / `SurfaceChildExitedNoop` | `class/surface_child_exited.rs`: noop widget (no template, `NOOP`, `close-request` emitted from `set_child_exited`); `Surface::child_exited` returns false for the noop, as in Zig. NB: Zig has no `1.2/surface-child-exited.blp` (the task text assumed one); the pre-Banner implementation is the Noop |
| `window.zig` `actionAbout` | `adw::AboutDialog` if `supports_dialogs()` else `gtk::show_about_dialog` |
| `application.zig` `signalCssParsingError` (GTK >= 4.16 branch) | `css.rs`: `domain code message` + `\n<source bytes>` (`CssSection::bytes`) behind `gtk_4_16` + runtime check, plain line otherwise; tested via `parsing_error_message` |
| blueprint variants | `build.rs` already compiles 1.0/1.2/1.3/1.4/1.5 variants (same list as `gresource.zig`) into the gresource; the class selects at build level (as Zig) |
### Verification
* `cargo clippy -p ghostty-apprt-gtk --all-targets -D warnings` clean with default
features AND with `default = []` (temporarily edited), i.e. both the new and
the fallback code compile.
* Default build: GTK app runs with the debug replay hook (paste dialog shows
`GhosttyClipboardConfirmationDialog` with the remember switch; no GTK criticals).
* Fallback build (`default = []`, run via the replay hook): the paste confirmation
and the close confirmation come up as `adw::MessageDialog`-based
`GhosttyClipboardConfirmationDialog` / `GhosttyCloseConfirmationDialog`
(transient for the window), the binary has no `BIND_NOW`.
* Workspace gate (excluding L1's in-progress `ghostty-lib`): `cargo clippy --workspace
--all-targets -D warnings`, `cargo test --workspace`, `cargo +nightly fmt --all` pass.
`+help`, `+list-actions [--docs]`, `+list-keybinds --help`, `+show-config --help`
are byte-identical to `/usr/bin/ghostty`.
### Deviation found by the runtime check
Zig's `GhosttyCloseConfirmationDialog` registers a `close-request` signal; with
the `MessageDialog` (a `GtkWindow`) parent that name already exists in the class
ancestry and GObject aborts. Zig's fallback has the same latent bug (dead code
upstream). The fallback build here names it `dialog-close-request`
(`close_confirmation_dialog.rs::CLOSE_REQUEST`); default build unchanged.
### Not testable here
Real old libraries (GTK < 4.16 / libadwaita < 1.5) are not installed; only the
compile-time fallback selection and the version arithmetic are exercised.
## 2-4
See the sub-notes. Deviations vs the task text: Zig `CodepointWidth` has no
`uucode` mode (bench); Zig does no file locking on Windows, so the faithful
port is a no-op there (cli).