fix: systematic byte fidelity for config strings and RV7 gaps

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
This commit is contained in:
ddidderr committed 2026-10-12 10:50:20 +02:00
1 parent ffc91c31e4
commit af094276f8
40 files changed
+986 -167

No files matched your search

+15 -23
View File
@@ -12,20 +12,6 @@ const DELIMITER: char = ';';
#[cfg(not(windows))]
const DELIMITER: char = ':';
/// Uppercase one UTF-16 code unit like Windows' `RtlUpcaseUnicodeChar`
/// (Unicode simple case mapping, per unit): surrogate halves and characters
/// whose uppercase is not a single BMP code unit are unchanged.
fn upcase_unit(u: u16) -> u16 {
let Some(c) = char::from_u32(u32::from(u)) else {
return u; // lone surrogate half
};
let mut up = c.to_uppercase();
match (up.next(), up.next()) {
(Some(m), None) => u16::try_from(u32::from(m)).unwrap_or(u),
_ => u,
}
}
/// The comparison form of an environment key on Windows: its WTF-16 code
/// units, uppercased per unit. Never lossy: distinct lone surrogates stay
/// distinct.
@@ -33,7 +19,7 @@ fn upcase_unit(u: u16) -> u16 {
fn windows_key(key: &OsStr) -> Vec<u16> {
crate::osbytes::wtf8_to_wide(&crate::osbytes::os_bytes(key))
.into_iter()
.map(upcase_unit)
.map(crate::windows_nls::upcase_w)
.collect()
}
@@ -400,20 +386,26 @@ mod tests {
#[cfg(test)]
mod windows_key_tests {
#[cfg(unix)]
use super::*;
fn key(units: &[u16]) -> Vec<u16> {
units.iter().copied().map(upcase_unit).collect()
units
.iter()
.copied()
.map(crate::windows_nls::upcase_w)
.collect()
}
#[test]
fn upcase_is_unicode_simple_per_unit() {
assert_eq!(upcase_unit(u16::from(b'a')), u16::from(b'A'));
assert_eq!(upcase_unit(0x00E9), 0x00C9); // e acute
assert_eq!(upcase_unit(0x03C9), 0x03A9); // omega
assert_eq!(upcase_unit(0x00DF), 0x00DF); // sharp s has no simple upper
assert_eq!(upcase_unit(0xD800), 0xD800);
assert_eq!(upcase_unit(0xDFFF), 0xDFFF);
fn keys_use_the_windows_table_not_unicode_mappings() {
// U+1F80 and U+1F88 are the same key under Windows/Zig's table even
// though Unicode's uppercase of U+1F80 is a two-unit sequence.
assert_eq!(key(&[0x1F80]), key(&[0x1F88]));
// The Georgian letters gained uppercase after the table froze.
assert_ne!(key(&[0x10D0]), key(&[0x1C90]));
// U+00DF stays distinct from "SS" (and from U+1E9E).
assert_ne!(key(&[0xDF]), key(&[0x53, 0x53]));
}
#[test]