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

+44 -6
View File
@@ -88,8 +88,9 @@ impl Surface {
// The options we'll use for all formatting. We'll just override the
// emit format.
let codepoint_map: Vec<formatter::CodepointMap> =
cfg.clipboard_codepoint_map.map.list.clone();
// Replacement strings are config strings (reversible byte encoding);
// the formatter produces text, so decode them to UTF-8 text first.
let codepoint_map = decode_codepoint_map(&cfg.clipboard_codepoint_map.map.list);
let opts = formatter::Options {
emit: Format::Plain, // We'll override this below
unwrap: true,
@@ -195,20 +196,20 @@ impl Surface {
pub fn complete_clipboard_request(
&self,
req: ClipboardRequest,
data: &str,
data: &[u8],
confirmed: bool,
) -> Result<(), ClipboardError> {
match req {
ClipboardRequest::Paste => self.complete_clipboard_paste(data.as_bytes(), confirmed),
ClipboardRequest::Paste => self.complete_clipboard_paste(data, confirmed),
ClipboardRequest::Osc52Read(clipboard) => {
self.complete_clipboard_read_osc52(data.as_bytes(), clipboard, confirmed)
self.complete_clipboard_read_osc52(data, clipboard, confirmed)
}
ClipboardRequest::Osc52Write(clipboard) => {
self.rt_surface.set_clipboard(
clipboard,
&[("text/plain".to_owned(), data.as_bytes().to_vec())],
&[("text/plain".to_owned(), data.to_vec())],
!confirmed,
);
Ok(())
@@ -407,3 +408,40 @@ impl Surface {
self.complete_clipboard_paste(text, true)
}
}
/// The clipboard codepoint map with string replacements decoded from the
/// config encoding (a literal `U+FDD0` is restored; bytes that are not
/// UTF-8 can't be represented in the formatter's text output).
fn decode_codepoint_map(list: &[formatter::CodepointMap]) -> Vec<formatter::CodepointMap> {
list.iter()
.map(|e| formatter::CodepointMap {
range: e.range,
replacement: match &e.replacement {
formatter::Replacement::String(s) => {
formatter::Replacement::String(ghostty_os::osbytes::config_str_to_text(s))
}
r @ formatter::Replacement::Codepoint(_) => r.clone(),
},
})
.collect()
}
#[cfg(test)]
mod decode_map_tests {
use super::*;
#[test]
fn string_replacements_are_decoded() {
let enc = ghostty_os::osbytes::valid_str_to_config_string("a\u{fdd0}");
assert_ne!(enc, "a\u{fdd0}");
let list = [formatter::CodepointMap {
range: [1, 2],
replacement: formatter::Replacement::String(enc),
}];
let out = decode_codepoint_map(&list);
assert_eq!(
out[0].replacement,
formatter::Replacement::String("a\u{fdd0}".to_owned())
);
}
}