Resolves the 9 gap groups in docs/rewrite/notes/review-rv7-codex.md and
audits every string-typed config field against its consumers
(field -> consumers -> decode status table in final-Z9.md):
- Windows EnvMap keys use a port of Zig's nls.zig uppercase table
(upcaseW); Windows locale init follows Zig's chain (setlocale +
_putenv_s); malformed WTF-8 now fails process launch conversions like
Zig instead of substituting U+FFFD; kitty file paths convert WTF-8.
- bash --rcfile/--init-file, configured TERM and inherited GTK LANG
keep their original bytes.
- C API clipboard completion and ghostty_surface_binding_action take
bytes; complete_clipboard_request is byte-typed through the core.
- text:/csi:/esc: bindings, enquiry-response, input = path:, embedded
initial_input, search needles, titles, link regexes, clipboard
codepoint map, command palette text and GTK font/namespace values
decode the escape encoding before use.
Remaining by design: D-Bus a{ss} values and +new-window arguments are
UTF-8 by D-Bus type. C API key/preedit text follows separately.
Test Plan:
- cargo test --workspace
- just check-macos (both), just check-windows, just check-wasm
9.5 KiB
Z9: RV7 gaps and the string-field decode audit
Builds on final-Z7.md / final-Z8.md (EncodedOs, the reversible U+FDD0
escape in ghostty_os::osbytes). Contract used below:
- encoded = a config/CLI
Stringin the reversible encoding. It must be decoded (EncodedOs::{to_os,to_path,to_bytes,to_wide},config_str_to_bytes, or for UTF-8-only UI textconfig_str_to_text) before it reaches the OS, the pty, the clipboard, or C. - plain = real text; it may only become an encoded value through
from_plain_text/valid_str_to_config_string(escapes a literalU+FDD0).
New helpers: osbytes::{InvalidWtf8, try_wtf8_to_wide, config_str_to_text},
EncodedOs::{try_to_wide, try_to_wide_nul, try_to_os},
ghostty_config::string::parse_bytes, ghostty_os::windows_nls::upcase_w.
1. The nine RV7 gaps
| # | Fix | Test |
|---|---|---|
| 1 Windows env key casing | windows_nls.rs is a port of std/os/windows/nls.zig (the 2544-entry l_intl.nls table and upcaseW); EnvMap keys use it per UTF-16 unit instead of char::to_uppercase |
windows_nls::tests::*, env::windows_key_tests::keys_use_the_windows_table_not_unicode_mappings (U+1F80 == U+1F88, Georgian distinct, no ß == SS) |
| 2 Windows locale init | locale.rs posix module now also builds on Windows: setlocale with Zig's literal LC_ALL = 6 (the UCRT rejects it exactly like Zig, so the chain continues) and _putenv_s for the LANG updates (unsafe with SAFETY comments). ensure_locale is only a no-op on wasm |
the mock-Sys chain tests run everywhere; Windows glue compiled by check-windows |
3 bash --rcfile |
GHOSTTY_BASH_RCFILE = EncodedOs::from_encoded(arg).to_os() |
bash_rcfile_keeps_original_bytes (byte FF, literal U+FDD0) |
4 configured TERM |
termio Config.term is now EncodedOs; Unix exports to_os(), Windows try_to_os()? |
configured_term_is_exported_decoded |
5 GTK inherited LANG |
saved_language is Option<OsString> read with var_os; the language config override is decoded; set_env takes AsRef<OsStr>; env restore factored into restore_language |
inherited_lang_keeps_non_utf8_bytes |
| 6 Windows Kitty paths | os_path_from_bytes: Unix bytes, Windows WTF-8 via EncodedOs::try_to_os (lone surrogates ok, malformed -> InvalidData, no empty-path fallback) |
payload_path_bytes_are_kept |
| 7 malformed WTF-8 | try_wtf8_to_wide (strict; lossy wtf8_to_wide kept for non-launch use). Command::start (path, cwd, argv) uses try_to_wide* -> StartError::InvalidWtf8; the Windows exec env block/overrides/GHOSTTY_RESOURCES_DIR/TERM/TERMINFO/PWD use try_to_os -> Error::InvalidWtf8 |
strict_wtf8_rejects_malformed_input, malformed_wtf8_env_overrides_fail (cfg(windows)) |
| 8 embedded C API | ghostty_surface_complete_clipboard_request passes bytes (CoreSurface::complete_clipboard_request / Surface::complete_clipboard_request now take &[u8]; the confirm callback gets the caller's text pointer unchanged); ghostty_surface_binding_action encodes the bytes with bytes_to_config_string before Action::parse |
clipboard_completion_and_binding_action_keep_raw_bytes (child receives 61 ff / 62 ff) |
| 9 text/csi/esc bindings | Text uses new string::parse_bytes; Csi/Esc write config_str_to_bytes(data) (128-byte limit applies to bytes). string::parse now escapes a literal U+FDD0 that an escape sequence produces |
config_loaded_text_csi_esc_write_decoded_bytes, string::tests::parse_bytes_decodes_the_reversible_encoding |
2. Systematic audit: config fields -> consumers -> decode status
"OK" = decoded at the boundary (or is the typed EncodedOs/bytes); "plain" =
guaranteed text with no OS meaning; "FIXED" = changed in Z9.
| Field / type | Consumers that reach OS / pty / clipboard / C | Status |
|---|---|---|
language (String) |
GTK LANG override |
FIXED (decoded; was raw encoded) |
font-family* (RepeatableString) |
Descriptor.family -> fontconfig / CoreText |
FIXED (config_str_to_text in shared_grid_set::Key::new; font names are UTF-8 for every backend) |
font-feature |
HarfBuzz/CoreText feature tags | plain (ASCII tags parsed from text) |
font-variation*, font-codepoint-map, adjust-*, font-style* |
descriptors/metrics | plain (tags, numbers, names) |
clipboard-codepoint-map replacement |
clipboard text from the formatter | FIXED (decode_codepoint_map; the formatter emits text, so non-UTF-8 replacement bytes can only become U+FFFD, a literal U+FDD0 is restored) |
title |
SetTitle action (all apprts, C set_title) |
FIXED (config_str_to_text in core + GTK window) |
class |
GTK application id (id_is_valid) |
plain (must be an ASCII-ish app id; encoded junk is rejected as invalid) |
window-title-font-family |
GTK CSS | FIXED (decoded) |
gtk-quick-terminal-namespace |
layer-shell namespace (&str API) |
FIXED (decoded text) |
macos-custom-icon |
only via C ghostty_config_get |
OK (config_capi::cstring_value decodes) |
term |
child env TERM (Unix, Windows) |
FIXED (EncodedOs through exec::Config) |
enquiry-response |
pty write on ENQ | FIXED (was String::into_bytes() of the encoded text; DerivedConfig.enquiry_response is now decoded Vec<u8>; test enquiry_response_is_decoded_to_bytes) |
command, initial-command (Command) |
exec argv/shell string, Windows CreateProcessW, flatpak ay, macOS login |
OK (decoded in command.rs config_str_to_bytes; Windows strict try_to_wide); window title from argv[0]/xdg-terminal-exec is FIXED (decoded text) |
env (RepeatableStringMap) |
child environment | OK (Unix config_str_to_os; Windows strict try_to_os) |
input (RepeatableReadableIO) |
pty write; file read | Raw: OK (decoded after clone_parsed). Path: FIXED, now typed EncodedOs (was opened undecoded); embedded initial_input escape works on bytes (string_escape) so parse_bytes(string_escape(b)) == b; test initial_input_escape_round_trips_bytes, readable_io_path_decodes_to_the_original_bytes |
link regex |
onig regex over terminal text | FIXED (decoded text; a pattern byte >0x7F that is not UTF-8 cannot match terminal text anyway) |
working-directory, background-image, custom-shader, gtk-custom-css, bell-audio-path, config-file, theme |
filesystem | OK (typed EncodedOs since Z7) |
command-palette-entry title/description |
GTK palette labels | FIXED (decoded text); its action follows the keybind row |
keybind payloads: text, csi, esc |
pty | FIXED (gap 9) |
keybind search: |
search needle bytes | FIXED (decoded; the GTK search entry encodes its plain UI text with valid_str_to_config_string) |
keybind set_surface_title, set_tab_title |
SetTitle action |
FIXED (decoded text) |
keybind activate_key_table* |
table-name lookup (config vs config) | plain/consistent (both sides encoded) |
keybind cursor_key |
pty | not config-parsable (raw String built in code) |
selection-word-chars |
codepoints | plain |
Diagnostic.{key,message} |
+validate-config stdout, +show-face stderr |
FIXED (print decoded bytes); GUI diagnostics are display text |
CLI/C: ghostty_init argv, ghostty_config_load_file, surface options (working_directory, command, env_vars, initial_input), ghostty_surface_text, clipboard completion, binding action, ghostty_config_get/open_path |
OK (bytes in, encoded or bytes) | |
C key event text, preedit |
String (lossy) |
documented limit: UTF-8 text by the ghostty.h contract (IME/key text); would need a byte KeyEvent.utf8 across ghostty-input |
3. OS data entering the system (byte preserving)
| Source | Entry point | Status |
|---|---|---|
| process environment | std::env::var_os / vars_os everywhere (get_env_map, getenv, homedir, xdg, shell_from_env, TMPDIR/TMP, GTK LANG, PATH); std::env::var remains only for ASCII-only developer switches (GHOSTTY_LOG, RUST_LOG, COLORFGBG, GHOSTTY_DEBUG_*) |
OK |
| argv | os::args (args_os / C ghostty_init bytes) -> collect_config_strings |
OK |
| config files, theme files | read as bytes -> encoded strings | OK |
passwd (getpwuid), uname |
bytes -> EncodedOs (hostname: text for comparison) |
OK |
| OSC 7 pwd, OSC 8 URIs, kitty file paths | bytes kept (set_pwd(&[u8]), os_path_from_bytes) |
OK |
| GTK file drops | os_bytes(path) into the shell-escaped paste |
OK |
| clipboard (C API, OSC 52, GTK text) | bytes (&[u8] in complete_clipboard_request; GTK text is UTF-8) |
OK |
| D-Bus / flatpak | cwd and argv ay bytes; environment is a{ss} (UTF-8 by D-Bus type) |
known limit |
+new-window over D-Bus |
arguments are GVariant strings | known limit (UTF-8 by type, as Zig) |
| Windows APIs | WTF-16 via EncodedOs::{from_wide,to_wide}; process launch is strict |
OK |
4. Typed where practical
exec::Config.term (EncodedOs), DerivedConfig.enquiry_response (Vec<u8>),
ReadableIO::Path (EncodedOs), ghostty_apprt::CoreSurface::complete_clipboard_request
(&[u8]). The remaining String config fields are either UI/plain text (above)
or go through the generic config machinery (macro, formatter, C value) where a
newtype would touch every field; each consumer is listed and decoded at its
boundary instead.
5. Gate
cargo test --workspace, clippy --workspace --all-targets -D warnings,
cargo +nightly fmt --all, just check-macos (both targets), just check-windows,
just check-wasm, +show-config --default --docs vs /usr/bin/ghostty (only the
gtk-opengl-debug line differs): see the final report. Windows and macOS code is
compile-checked, not run natively.