fix: +show-face u21/UTF-8 semantics, Zig output buffering, isAbsolute

Resolves RV14 (docs/rewrite/notes/review-rv14-codex.md, final-Z17.md):
- +show-face --cp parses as u21 (new U21 field type): out-of-range
  values give an invalid-value diagnostic and exit 1 instead of a panic;
  --string accepts raw bytes and fails with error.InvalidUtf8 after the
  codepoint lookup like Zig.
- CLI actions write through ZigWriter, a model of Zig 0.15's buffered
  File.writer (fixed buffer, drain on overflow, explicit flush), so
  stdout/stderr interleave in Zig's flush order (+show-face, help,
  version, validate-config, show-config, list-*, ssh-cache, crash-report,
  edit-config, new-window).
- Zig 0.15.2 std.fs.path.isAbsolute port (Windows: \theme and /theme are
  absolute) replaces Path::is_absolute at every Zig isAbsolute site.
- io_uring is available iff a 256-entry ring can be created, like
  libxev; the reactor falls back to IORING_OP_TIMEOUT without EXT_ARG.
- Windows basename (`λ:theme`) verified against 0.15.2 (test added).

Test Plan:
- cargo test --workspace --no-fail-fast
- diff +show-face outputs (pipe) against /usr/bin/ghostty
This commit is contained in:
ddidderr committed 2026-10-12 15:15:18 +02:00
1 parent 6394bc65d3
commit f76179f49a
26 files changed
+574 -55

No files matched your search

+70
View File
@@ -81,6 +81,36 @@ pub fn os_bytes(s: &OsStr) -> Vec<u8> {
s.to_str().map_or_else(Vec::new, |s| s.as_bytes().to_vec())
}
/// `std.fs.path.isAbsolutePosix` (Zig 0.15.2).
#[must_use]
pub fn zig_is_absolute_posix(path: &[u8]) -> bool {
path.first() == Some(&b'/')
}
/// `std.fs.path.isAbsoluteWindows` (Zig 0.15.2): any leading separator (a
/// rooted or UNC path) or `<byte>:` followed by a separator is absolute.
#[must_use]
pub fn zig_is_absolute_windows(path: &[u8]) -> bool {
match path {
[] => false,
[b'/' | b'\\', ..] => true,
[_, b':', b'/' | b'\\', ..] => true,
_ => false,
}
}
/// `std.fs.path.isAbsolute` for the host platform. Rust's
/// `Path::is_absolute` differs on Windows (it requires a prefix, so `\theme`
/// and `/theme` are not absolute there).
#[must_use]
pub fn zig_is_absolute(path: &[u8]) -> bool {
if cfg!(windows) {
zig_is_absolute_windows(path)
} else {
zig_is_absolute_posix(path)
}
}
/// Build an OS string from raw bytes (WTF-8 on Windows; bytes that are not
/// well-formed WTF-8 become U+FFFD). Filesystem and process operations must
/// use their checked conversions instead.
@@ -290,6 +320,12 @@ impl EncodedOs {
Self::from_os(p.as_os_str())
}
/// `std.fs.path.isAbsolute` on the decoded bytes.
#[must_use]
pub fn is_zig_absolute(&self) -> bool {
zig_is_absolute(&self.to_bytes())
}
/// Encode raw bytes.
#[must_use]
pub fn from_bytes(b: &[u8]) -> Self {
@@ -798,3 +834,37 @@ mod tests {
assert_eq!(config_str_to_os(&s), o);
}
}
#[cfg(test)]
mod zig_absolute_tests {
use super::*;
#[test]
fn windows_is_absolute_matches_zig_0_15_2() {
for (p, want) in [
("", false),
("/", true),
("//server/file", true),
("\\\\server", true),
("\\theme", true),
("/theme", true),
("c", false),
("c:", false),
("c:\\", true),
("c:/", true),
("1:/x", true),
("C:cwd/another", false),
("directory\\directory", false),
] {
assert_eq!(zig_is_absolute_windows(p.as_bytes()), want, "{p}");
}
}
#[test]
fn posix_is_absolute_matches_zig_0_15_2() {
assert!(!zig_is_absolute_posix(b""));
assert!(zig_is_absolute_posix(b"/home/foo"));
assert!(!zig_is_absolute_posix(b"bar/"));
assert!(!zig_is_absolute_posix(b"\\theme"));
}
}
+15 -16
View File
@@ -108,24 +108,13 @@ pub fn available(be: Backend) -> bool {
}
}
/// libxev performs a harmless `io_uring_setup(256)` to see if we have
/// io_uring support. We additionally require what our loop needs
/// (`IORING_OP_POLL_ADD`, `IORING_OP_POLL_REMOVE` and the `EXT_ARG` timeout),
/// so that "available" never selects a ring that cannot run the IO loop.
/// libxev's `io_uring.available()`: a harmless `IoUring.init(256, 0)` (an
/// `io_uring_setup` with 256 entries and no flags) that either succeeds or
/// not. Nothing more is required; a reactor that wants a feature the ring
/// lacks (such as the `EXT_ARG` wait timeout) has to cope with its absence.
#[cfg(target_os = "linux")]
fn io_uring_available() -> bool {
let Ok(ring) = io_uring::IoUring::new(256) else {
return false;
};
if !ring.params().is_feature_ext_arg() {
return false;
}
let mut probe = io_uring::Probe::new();
if ring.submitter().register_probe(&mut probe).is_err() {
return false;
}
probe.is_supported(io_uring::opcode::PollAdd::CODE)
&& probe.is_supported(io_uring::opcode::PollRemove::CODE)
io_uring::IoUring::new(256).is_ok()
}
#[cfg(not(target_os = "linux"))]
@@ -195,6 +184,16 @@ mod tests {
assert_eq!(backend(), before);
}
#[cfg(target_os = "linux")]
#[test]
fn io_uring_available_is_plain_setup() {
// libxev's check is exactly a successful `IoUring.init(256, 0)`.
assert_eq!(
available(Backend::IoUring),
io_uring::IoUring::new(256).is_ok()
);
}
#[cfg(target_os = "linux")]
#[test]
fn prefer_epoll_then_detect() {