Files
ghostty-rs/docs/rewrite/notes/final-Z11.md
T
ddidderr 79a7fd4f76 fix(config): match Zig's numeric grammar, NUL handling and diagnostics
Resolves the 6 differential gap groups in docs/rewrite/notes/review-
rv9-codex.md (details in final-Z11.md):
- ghostty_os::zig_fmt ports std.fmt.parseInt/parseUnsigned/parseFloat
  (underscores, 0x/0o/0b prefixes, hex floats, inf/nan) with the Zig std
  tests and replaces every int/float parse in config, keybind
  parameters, ssh-cache and desktop detection.
- Interior NUL no longer panics in font names and ends C strings like
  Zig's backing bytes (fontconfig, C config getters, title actions,
  clipboard callback, relative paths, config-file, shaders, background
  image, GTK CSS).
- GTK new-window arguments are trimmed of ASCII whitespace only.
- Command tokenization uses a port of std.process.ArgIteratorGeneral
  (incl. CR/LF separators) with its std tests.
- UTF-8 validation happens where Zig's field parsers do it, so error
  kinds match (metric modifiers, quick-terminal size, link, ...).

A scripted differential run of 202 fields x ~220 hostile/valid values
against /usr/bin/ghostty (+validate-config and +show-config: exit code,
stdout, stderr) now has zero mismatches besides build-specific defaults
(theme resources path, auto-update-channel, gtk-opengl-debug); it also
surfaced and fixed background-blur failure state, NaN formatting and
NaN/-0 finalize clamps. Regression tests: ghostty-config/tests/
zig_parity.rs.

Test Plan:
- cargo test --workspace --no-fail-fast (3456 passed, exit 0)
- just check-macos (both), just check-windows, just check-wasm
2026-10-12 12:35:37 +02:00

73 lines
4.2 KiB
Markdown

# Z11: RV9 gap groups (Ghostty 1.3.1 parity)
Fixes for the six gap groups in `review-rv9-codex.md`. No version change, no commit.
## Fixes
1. **Interior NUL in font names.** `fontconfig::Pattern::add_string_bytes`
ends the value at the first NUL (the C-string handoff Zig does) instead of
failing, so `font-family`, `font-style` and codepoint-map families no longer
hit the `assert!` in `discovery::to_fc_pattern`.
Test: `discovery::tests::interior_nul_in_font_names_ends_the_c_string`.
2. **Numeric grammars.** New `ghostty_os::zig_fmt`: exact ports of
`std.fmt.parseInt`/`parseUnsigned` (underscores, `0b/0o/0x` in both cases,
sign rules, `len > 2` prefix gate, Overflow vs InvalidCharacter) and
`std.fmt.parseFloat` (grammar of `parse.zig` incl. `validUnderscores`, hex
floats via a port of `convertHex` incl. the many-digits path, `inf`/
`infinity`/`nan`). Decimal values are rounded by Rust's correctly rounded
`FromStr` once the Zig grammar accepted the text. Ported all std tests.
Used by: primitive int/float fields (base 0), palette key (u8, base 0, Zig
error names), variation values, mouse-scroll bare float, quick-terminal
`px`/`%`, window-padding (u32, base 10), duration (`parseUnsigned` u64),
background-blur radius, codepoint/clipboard map hex (base 16), metric
modifiers, keybind numeric parameters (base 10 / float), ssh-cache entry
timestamp, `GIO_LAUNCHED_DESKTOP_FILE_PID`.
Also found by differential runs: Zig `{d}` prints `nan` (Rust `NaN`);
finalize clamps use Zig `@min/@max/clamp` NaN and +-0 semantics
(`cli_args::zig_min/zig_max/zig_clamp`); a failed `background-blur` radius
parse leaves `radius` (0 or the previous radius) like Zig's in-place union
initialisation.
3. **C strings.** `config_capi` getters, C action titles/strings
(`action_c::Keep`) and the embedded clipboard write callback now end the C
string at the first NUL instead of deleting NUL bytes.
4. **GTK new-window args.** `--working-directory=`/`--title=` trim only
`std.ascii.whitespace` (space, \t, \n, \r, VT, FF); U+00A0 etc. survive.
5. **Shell tokenizer.** New `ghostty_os::arg_iterator::ArgIteratorGeneral`
(port of `std.process.ArgIteratorGeneral`, including `comments`/
`single_quotes` options, `skip`, NUL terminates). `Command::arg_iterator`
(and so shell-integration detection) use it: space, tab, CR and LF split.
Ported the std "response file arg parsing" test plus separator/quote/NUL
tests; termio test `detect_shell_splits_on_all_zig_separators`.
6. **Parser diagnostics.** UTF-8 validation moved to where Zig would notice it:
metric modifiers (`InvalidFormat`), quick-terminal size (`MissingUnit`/
`InvalidValue`), `link` (`NotImplemented`), colors/palette
(`InvalidCharacter`), background-blur, keybind trigger (parsed after the
action, like `Parser.init`) and action parameters (known action with bad
bytes is `InvalidFormat`, unknown name `InvalidAction`).
Other differences found: config-file open errors are printed as
`error.FileNotFound` etc.; paths with interior NUL resolve/open as the C
string prefix (`EncodedOs::to_syscall_path`) for relative path expansion,
`config-file`, custom shaders, background image, gtk css.
## Differential harness
Script (not committed): for each of the 202 `Config.zig` fields x ~220 hostile
and valid values, run `/usr/bin/ghostty +validate-config --config-file=F` and
`+show-config` (value in `$XDG_CONFIG_HOME/ghostty/config`) against the Rust
binary and compare exit code, stdout and stderr. Final state: zero mismatches
except `theme` (resources path differs by install location),
`auto-update-channel` default (`tip` vs `stable`, build channel) and
`gtk-opengl-debug` (debug build).
Regression tests: `crates/ghostty-config/tests/zig_parity.rs`,
`ghostty_os::zig_fmt::tests`, `ghostty_os::arg_iterator::tests`,
`ghostty-config` command tests, lib/gtk tests listed above.
## Not covered / notes
- `ghostty-terminal` OSC/tmux/kitty parsers also call `parseInt`
(`terminal/osc/parsers/*.zig`, `tmux/*.zig`, `kitty/*.zig`); they are outside
the config/input scope of this task and still use Rust `str::parse`/
`from_str_radix`. `zig_fmt` is available for a follow-up.
- Windows/macOS behaviour is covered by cross-checks only.