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:
1 parent
3aab04bdce
commit
654acd55e6
26 files changed
+574
-55
No files matched your search
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Reference in new issue
Block a user