fix: last RV11 gaps - bench enum args, theme NUL paths, Zig error names

Resolves the 3 remaining gaps in docs/rewrite/notes/review-rv11-codex.md
(final-Z14.md):
- ghostty-bench validates every occurrence of enum options in order
  (Args::take_enum; empty value resets) like cli/args.zig, so
  `--mode=bad --mode=noop` fails as in Zig.
- Theme paths with an interior NUL open the prefix like Zig's C-string
  path handling (verified against /usr/bin/ghostty).
- Filesystem diagnostics print Zig's error names (error.FileNotFound,
  NotDir, AccessDenied, IsDir, NameTooLong, SymLinkLoop, ...) via a
  shared ghostty_os::zig_error mapping used by theme/path/config-file/
  file_load/edit-config/list-themes; messages match the oracle.

Test Plan:
- cargo test --workspace --no-fail-fast
- just check-macos (both), just check-windows, just check-wasm
This commit is contained in:
ddidderr committed 2026-10-12 13:54:15 +02:00
1 parent 64aa8b6793
commit 4bbe0d7fe9
12 files changed
+215 -89

No files matched your search

+43 -6
View File
@@ -128,7 +128,13 @@ pub(crate) fn special_kind_name(_: std::fs::FileType) -> &'static str {
fn check_regular(file: &File, theme: &str, diags: &mut DiagnosticList) -> bool {
match file.metadata() {
Err(err) => {
diag(diags, format!("not reading theme from \"{theme}\": {err}"));
diag(
diags,
format!(
"not reading theme from \"{theme}\": {}",
ghostty_os::zig_error::io_error_name(&err)
),
);
false
}
Ok(m) if !m.is_file() => {
@@ -197,7 +203,7 @@ pub fn open(
let mut it = LocationIterator::new(resources_dir);
while let Some((_, dir)) = it.next_location() {
let path = EncodedOs::from_path(&dir.to_path().ok()?.join(&theme_path));
match path.to_path().and_then(File::open) {
match path.to_syscall_path().and_then(File::open) {
Ok(file) => {
if !check_regular(&file, theme_text, diags) {
return None;
@@ -211,8 +217,9 @@ pub fn open(
diag(
diags,
format!(
"failed to load theme \"{theme_text}\" from the file \"{}\": {err}",
path.as_encoded()
"failed to load theme \"{theme_text}\" from the file \"{}\": {}",
path.as_encoded(),
ghostty_os::zig_error::io_error_name(&err)
),
);
return None;
@@ -241,7 +248,7 @@ pub fn open(
/// then messages will be appended to the given error list and None is
/// returned.
pub fn open_absolute(theme: &EncodedOs, diags: &mut DiagnosticList) -> Option<File> {
let path = theme.to_path();
let path = theme.to_syscall_path();
let theme = theme.as_encoded();
match path.and_then(File::open) {
Ok(f) => Some(f),
@@ -254,7 +261,10 @@ pub fn open_absolute(theme: &EncodedOs, diags: &mut DiagnosticList) -> Option<Fi
} else {
diag(
diags,
format!("failed to load theme from the path \"{theme}\": {err}"),
format!(
"failed to load theme from the path \"{theme}\": {}",
ghostty_os::zig_error::io_error_name(&err)
),
);
}
None
@@ -282,6 +292,33 @@ mod tests {
);
}
#[cfg(unix)]
#[test]
fn theme_path_errors_use_zig_names() {
let mut d = DiagnosticList::default();
assert!(open(&e("/etc/passwd/x"), None, &mut d).is_none());
assert_eq!(
d.items()[0].message,
"failed to load theme from the path \"/etc/passwd/x\": error.NotDir"
);
}
#[cfg(unix)]
#[test]
fn theme_absolute_interior_nul_opens_prefix() {
let dir = std::env::temp_dir().join(format!("gtheme-nul-{}", std::process::id()));
let _ = std::fs::create_dir_all(&dir);
let f = dir.join("t");
std::fs::write(&f, "background = #112233\n").unwrap();
let mut bytes = f.as_os_str().as_encoded_bytes().to_vec();
bytes.extend_from_slice(b"\0tail");
let t = EncodedOs::from_bytes(&bytes);
let mut d = DiagnosticList::default();
assert!(open(&t, None, &mut d).is_some());
assert_eq!(d.items().len(), 0);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn theme_absolute_missing_and_dir() {
let mut d = DiagnosticList::default();