Zig parses config values (cli/args.zig, Config.zig field parsers, config/string.zig) from raw byte slices, and each field type decides whether it requires UTF-8 (InvalidValue otherwise) or stores bytes. The port escape-encoded raw bytes into Strings at the line reader and let fields parse the escaped text, so fields could observe encoding artifacts; successive audits kept finding such leaks (RV5-RV8). Field parsers now receive the original bytes: text fields validate UTF-8 where Zig does, byte/path fields store bytes (EncodedOs/OsString). This resolves all 9 RV8 findings (docs/rewrite/notes/review-rv8-codex.md), each with a regression test: Windows path conversion fails with BadPathName instead of substituting U+FFFD; Windows process launch uses strict UTF-8 like Zig's Command; the WTF-8 decoder accepts paired- surrogate encodings like Zig; literal U+FDD0 Unicode key triggers; selection-word-chars and clipboard-codepoint-map reject invalid UTF-8; font style / codepoint-map family names use original bytes; C title actions forward bytes; D-Bus new-window/Flatpak conversions match Zig. Design, sweep and validation: docs/rewrite/notes/final-bytes-refactor.md. Also moves the bundled iTerm2-Color-Schemes license out of resources/themes/ so +list-themes no longer lists it as a theme (output is now byte-identical to Ghostty 1.3.1). Done by Codex (gpt-6-astra), reviewed and verified by the orchestrator. Test Plan: - cargo test --workspace --no-fail-fast (3428 passed, exit 0) - just check-macos (both), just check-windows, just check-wasm - +show-config --default --docs, +list-keybinds, +list-actions --docs, +list-themes --plain compared with /usr/bin/ghostty
100 lines
2.9 KiB
Rust
100 lines
2.9 KiB
Rust
//! `ConfigField` implementations for types owned by `ghostty-input`
|
|
//! (the input crate cannot depend on the config crate).
|
|
|
|
use std::fmt;
|
|
|
|
use ghostty_input::{KeyRemapSet, OptionAsAlt};
|
|
|
|
use crate::{
|
|
c_value::CValue,
|
|
cli_args::{ArgError, ConfigField},
|
|
formatter::EntryFormatter,
|
|
};
|
|
|
|
impl ConfigField for KeyRemapSet {
|
|
fn parse_bytes(&mut self, value: Option<&[u8]>) -> Result<(), ArgError> {
|
|
let value = crate::cli_args::text_value(value)?;
|
|
self.parse_cli(value).map_err(|_| ArgError::InvalidValue)
|
|
}
|
|
|
|
fn format_entry(&self, f: &mut EntryFormatter<'_>) -> fmt::Result {
|
|
let entries = self.format_entries();
|
|
if entries.is_empty() {
|
|
return writeln!(f.writer, "{} = ", f.name);
|
|
}
|
|
for e in entries {
|
|
f.format_entry(&e)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl ConfigField for OptionAsAlt {
|
|
const VALID_VALUES: Option<&'static [&'static str]> = None;
|
|
|
|
fn c_value(&self) -> CValue {
|
|
CValue::EnumName(match self {
|
|
OptionAsAlt::False => "false",
|
|
OptionAsAlt::True => "true",
|
|
OptionAsAlt::Left => "left",
|
|
OptionAsAlt::Right => "right",
|
|
})
|
|
}
|
|
|
|
fn parse_bytes(&mut self, value: Option<&[u8]>) -> Result<(), ArgError> {
|
|
let value = crate::cli_args::text_value(value)?;
|
|
let v = value.ok_or(ArgError::ValueRequired)?;
|
|
*self = match v {
|
|
"false" => OptionAsAlt::False,
|
|
"true" => OptionAsAlt::True,
|
|
"left" => OptionAsAlt::Left,
|
|
"right" => OptionAsAlt::Right,
|
|
_ => return Err(ArgError::InvalidValue),
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
fn format_entry(&self, f: &mut EntryFormatter<'_>) -> fmt::Result {
|
|
let s = match self {
|
|
OptionAsAlt::False => "false",
|
|
OptionAsAlt::True => "true",
|
|
OptionAsAlt::Left => "left",
|
|
OptionAsAlt::Right => "right",
|
|
};
|
|
writeln!(f.writer, "{} = {}", f.name, s)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[allow(clippy::unwrap_used)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn fe<T: ConfigField>(name: &str, v: &T) -> String {
|
|
let mut s = String::new();
|
|
crate::formatter::format_entry(name, v, &mut s).unwrap();
|
|
s
|
|
}
|
|
|
|
#[test]
|
|
fn remap_set_format_entry_empty() {
|
|
assert_eq!(fe("key-remap", &KeyRemapSet::default()), "key-remap = \n");
|
|
}
|
|
|
|
#[test]
|
|
fn remap_set_format_entry_values() {
|
|
let mut set = KeyRemapSet::default();
|
|
ConfigField::parse_cli(&mut set, Some("ctrl=super")).unwrap();
|
|
let out = fe("key-remap", &set);
|
|
assert!(out.starts_with("key-remap = left_ctrl=left_super\n"));
|
|
}
|
|
|
|
#[test]
|
|
fn option_as_alt_roundtrip() {
|
|
let mut v = OptionAsAlt::default();
|
|
v.parse_cli(Some("left")).unwrap();
|
|
assert_eq!(fe("a", &v), "a = left\n");
|
|
assert_eq!(v.parse_cli(Some("x")), Err(ArgError::InvalidValue));
|
|
}
|
|
}
|