Files
ddidderr 33d15a2dc2 feat(config): port the configuration system and CLI argument parser
Ports ZIG/src/config (minus C/WASM APIs) and cli/{args,CommaSplitter,
diagnostics}.zig into ghostty-config:
- all 202 options with exact names, types, defaults and verbatim docs,
  declared through one config_struct! macro; Zig's comptime reflection is
  replaced by the ConfigField/CliTarget traits
- loading: default XDG files, -e, config-file includes (optional '?',
  cycle detection), themes incl. light:/dark: pairs, conditional config,
  replay, finalize() derived defaults, clone/changed diffing for reload
- Linux/GTK default keybinds built on ghostty_input::binding::Set
- formatter producing +show-config output

Verified against the installed Ghostty 1.3.1: `+show-config --default`
and `--default --docs` match byte for byte (except gtk-opengl-debug,
which differs between debug/release builds), as do two complex loaded
configs.

Open items (docs/rewrite/notes/ghostty-config.md): url regex needs an
Oniguruma-compatible engine (onig) in the renderer; some font/terminal
types are local copies awaiting integration; KeyRemapSet equality is
order sensitive unlike Zig.

Test Plan:
- cargo test -p ghostty-config (255 tests)
- compare `ghostty +show-config --default --docs` with our formatter
2026-10-11 22:00:12 +02:00

80 lines
4.9 KiB
Markdown

# ghostty-config port notes (W6)
Sub-notes from the parallel forks live in `ghostty-config/` (`files-a.md`,
`types-a.md`, `types-b.md`, `keybinds.md`).
## What was ported (Zig -> Rust, all in `crates/ghostty-config/src/`)
| Zig | Rust |
|---|---|
| cli/args.zig | `cli_args.rs` (`ConfigField` + `CliTarget` traits, `parse`, `LineIterator`, `ArgsIterator`, `parse_auto_struct_with`, `parse_packed_struct_with`, `split_tagged_union`, `compatibility_renamed`) |
| cli/CommaSplitter.zig | `comma_splitter.rs` |
| cli/diagnostics.zig | `diagnostics.rs` |
| config/Config.zig (fields + methods) | `config.rs` (fields declared in a single `config_struct!` invocation: name, type, default, verbatim doc comment), `keys.rs` (one `Key` const per option) |
| Config.zig nested types | `types_a.rs`, `types_b.rs`, `keybinds.rs`, `font_types.rs`, `metric_modifier.rs`, `input_fields.rs` (ConfigField impls for ghostty-input types) |
| conditional.zig, key.zig, ErrorList.zig | `conditional.rs`, `key.rs`, `error_list.rs` |
| formatter.zig | `formatter.rs` (EntryFormatter) + `file_formatter.rs` (FileFormatter) |
| command/path/url/string/io/theme/edit/file_load/RepeatableStringMap/ClipboardCodepointMap | same names, snake_case |
| config/config-template, config/testdata | `src/config-template`, `src/testdata/` |
| helpgen.zig (config part) | `macros::help_string` (computed at runtime from the doc attributes) |
Skipped: CApi.zig, c_get.zig, Wasm.zig, all `cval`/`*_c` members, `enum(c_int)`
discriminants, `getGObjectType`.
## Design
* No comptime reflection: `config_struct!` (macros.rs) generates the struct,
`parse_field` (== `parseIntoField`, incl. "empty value resets to default"),
per-field `FieldInfo` (name, doc lines, deep-eq fn, format fn) used by
`FileFormatter`, `Config::changed`, `ChangeIterator`.
* `config_enum!` / `config_packed!` define Zig-style enums / packed bool structs.
* Memory: all values are owned (`String`, `Vec`); there is no arena. `clone` is
`#[derive(Clone)]`; Zig `equal` == `PartialEq` (custom where Zig's differs).
* Replay (theme / conditional state): `Config::load_replay` replaces the Zig
`Replay.Iterator` (which aliased the config); behaviour is the same.
* `Config::default()` == Zig `Config.default` (default keybinds, command palette
entries and the URL link are installed). `Config::raw_default()` == bare `.{}`.
* `Config::load()` == Zig `Config.load` (default files, CLI, recursive files,
finalize). `finalize` skips the shell/passwd lookup under `cfg(test)` like Zig's
`builtin.is_test`.
* Linux values are baked in for `builtin.os.tag` switches (e.g. `font-size = 12`,
`copy-on-select = true`, `quit-after-last-window-closed = true`,
`linux-cgroup = single-instance`, `alpha-blending = linear-corrected`).
`gtk-opengl-debug` follows `cfg!(debug_assertions)` (Zig: `mode == .Debug`).
`auto-update-channel` finalizes to `stable` (upstream release build value).
* macOS-only options (`macos-*`) are parsed and stored but otherwise ignored.
## Verification against the real binary
`ghostty +show-config --default` and `--default --docs` (Ghostty 1.3.1 installed
at /usr/bin/ghostty) were diffed against `FileFormatter` output of
`Config::default()`: identical except `gtk-opengl-debug` (debug vs release build).
A loaded config (themes light/dark, config-file includes incl. `?` optional
and missing, keybinds, palette, font-codepoint-map, errors/diagnostics) was also
diffed against `ghostty +show-config`; identical except `auto-update-channel`.
## Tests
Every Zig `test` in the ported files is ported (names snake_cased), except:
* args.zig: none skipped (36 Zig tests -> 40+ Rust tests incl. extras)
* Config.zig: C-API tests ("RepeatableCommand cval", "cval cleared") skipped
* key.zig `Value` (comptime type check) replaced by `value_key_types`
* Allocator-failure tests: none existed.
Extra tests added for config-file cycles / optional includes, docs formatting.
## Deviations / notes for integration
* `ArgError::Other(name)` renders as `unknown error error.<name>` like Zig's `{}` of an error.
* Error text for IO failures in `config-file` diagnostics uses Rust wording except
`FileNotFound`/`AccessDenied`.
* `theme::open` takes the resources dir explicitly (`ghostty_os::resources_dir().app()`).
* URL regex (`url::REGEX`) needs Oniguruma-style lookbehind: consumers (renderer
link matching) must use the `onig` crate (added as dev-dependency only here).
* `font_types.rs` holds local `Variation`, `Descriptor`, `CodepointMap`;
`clipboard_codepoint_map::Entry` mirrors terminal/formatter's CodepointMap
(TODO(integration): ghostty-font / ghostty-terminal should re-use these).
* `metric_modifier::MetricModifier` is the font `Metrics.Modifier` (font crate depends on config).
* `CursorStyle` is defined in `types_a.rs` (cannot depend on ghostty-terminal).
* macOS app-support config paths are not implemented (Linux only).
* `KeyRemapSet::PartialEq` is input's derived equality (Zig `equal` is order independent).