fix(config): port Zig 0.15.2 basename (not 0.16) for theme names
The first basename port followed Zig 0.16's ComponentIterator-based implementation (the system std); Ghostty 1.3.1 builds with 0.15.2. The theme-name check now ports 0.15.2's basenamePosix/basenameWindows: a ':' at byte 1 ends the drive designator for any first byte, so '1:theme' yields 'theme'. Tests use the 0.15.2 cases.
This commit is contained in:
1 parent
0216443764
commit
3068e3a389
2 files changed
+36
-108
No files matched your search
@@ -160,107 +160,51 @@ fn zig_basename(path: &str) -> &str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `std.fs.path.basenamePosix`: the last `ComponentIterator` component.
|
/// `std.fs.path.basenamePosix` (Zig 0.15.2). `.` and `..` are ordinary names.
|
||||||
/// `.` and `..` are ordinary names.
|
|
||||||
fn basename_posix(path: &str) -> &str {
|
fn basename_posix(path: &str) -> &str {
|
||||||
let root_len = usize::from(path.as_bytes().first() == Some(&b'/'));
|
basename_impl(path, |b, _| b == b'/')
|
||||||
basename_inner(path, root_len, |b| b == b'/')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `std.fs.path.basenameWindows`.
|
/// `std.fs.path.basenameWindows` (Zig 0.15.2): `/` and `\\` separate, and a
|
||||||
|
/// `:` at byte index 1 ends a drive designator of any first byte (`1:theme`
|
||||||
|
/// is `theme`).
|
||||||
fn basename_windows(path: &str) -> &str {
|
fn basename_windows(path: &str) -> &str {
|
||||||
basename_inner(path, parse_path_windows_root_len(path), |b| {
|
basename_impl(path, |b, i| {
|
||||||
b == b'/' || b == b'\\'
|
b == b'/' || b == b'\\' || (b == b':' && i == 1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `ComponentIterator.init` + `last()`: skip the root and any separators after
|
/// The shared loop of both Zig functions. `is_stop(byte, index)` is true for
|
||||||
/// it, then take the last component (empty if there is none).
|
/// separators (and, on Windows, the drive colon).
|
||||||
fn basename_inner(path: &str, root_len: usize, is_sep: impl Fn(u8) -> bool) -> &str {
|
fn basename_impl(path: &str, is_stop: impl Fn(u8, usize) -> bool) -> &str {
|
||||||
let bytes = path.as_bytes();
|
let bytes = path.as_bytes();
|
||||||
let mut root_end_index = root_len;
|
if bytes.is_empty() {
|
||||||
for &c in &bytes[root_len..] {
|
|
||||||
if !is_sep(c) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
root_end_index += 1;
|
|
||||||
}
|
|
||||||
let mut end = bytes.len();
|
|
||||||
loop {
|
|
||||||
if end == root_end_index {
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if !is_sep(bytes[end - 1]) {
|
// Trailing separators (a drive colon at index 1 ends the path: empty).
|
||||||
|
let mut end_index = bytes.len() - 1;
|
||||||
|
loop {
|
||||||
|
let byte = bytes[end_index];
|
||||||
|
if is_stop(byte, end_index) {
|
||||||
|
// Zig's Windows loop only skips real separators; the colon case
|
||||||
|
// returns empty at once, which is the same as skipping to 0.
|
||||||
|
if byte == b':' || end_index == 0 {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
end_index -= 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
end -= 1;
|
let mut start_index = end_index;
|
||||||
|
end_index += 1;
|
||||||
|
while !is_stop(bytes[start_index], start_index) {
|
||||||
|
if start_index == 0 {
|
||||||
|
return &path[..end_index];
|
||||||
}
|
}
|
||||||
let mut start = end;
|
start_index -= 1;
|
||||||
while start != root_end_index && !is_sep(bytes[start - 1]) {
|
|
||||||
start -= 1;
|
|
||||||
}
|
|
||||||
&path[start..end]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `std.fs.path.parsePathWindows(u8, path).root.len` (with
|
|
||||||
/// `getWin32PathType`; the path is WTF-8 so the drive letter is one whole code
|
|
||||||
/// point of at most three bytes, like `RtlDetermineDosPathNameType_U`).
|
|
||||||
fn parse_path_windows_root_len(path: &str) -> usize {
|
|
||||||
let b = path.as_bytes();
|
|
||||||
let sep = |c: u8| c == b'/' || c == b'\\';
|
|
||||||
let Some(&first) = b.first() else {
|
|
||||||
return 0; // relative
|
|
||||||
};
|
|
||||||
if sep(first) {
|
|
||||||
// \x
|
|
||||||
if b.len() < 2 || !sep(b[1]) {
|
|
||||||
return 1; // rooted
|
|
||||||
}
|
|
||||||
// \\. or \\?
|
|
||||||
if b.len() > 2 && (b[2] == b'.' || b[2] == b'?') {
|
|
||||||
// exactly \\. or \\? with nothing trailing
|
|
||||||
if b.len() == 3 {
|
|
||||||
return b.len(); // root_local_device
|
|
||||||
}
|
|
||||||
// \\.\x or \\?\x
|
|
||||||
if sep(b[3]) {
|
|
||||||
return 4; // local_device
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// \\x: UNC absolute (`parseUNC`)
|
|
||||||
let Some(server_end) = b[2..].iter().position(|&c| sep(c)).map(|i| i + 2) else {
|
|
||||||
return b.len();
|
|
||||||
};
|
|
||||||
// Any number of separators may sit between the server and the share.
|
|
||||||
let share_start =
|
|
||||||
server_end + 1 + b[server_end + 1..].iter().take_while(|&&c| sep(c)).count();
|
|
||||||
if share_start == b.len() {
|
|
||||||
// No share: the share is the empty slice right after the server
|
|
||||||
// separator.
|
|
||||||
return server_end + 1;
|
|
||||||
}
|
|
||||||
let share_end = b[share_start..]
|
|
||||||
.iter()
|
|
||||||
.position(|&c| sep(c))
|
|
||||||
.map_or(b.len(), |i| i + share_start);
|
|
||||||
// `sep_after_share` adds one more.
|
|
||||||
return share_end + usize::from(share_end != b.len());
|
|
||||||
}
|
|
||||||
// Drive letter: a code point of 1..=3 bytes followed by ':'.
|
|
||||||
let colon_i = match first {
|
|
||||||
0x00..=0x7f => 1,
|
|
||||||
0xc2..=0xdf => 2,
|
|
||||||
0xe0..=0xef => 3,
|
|
||||||
_ => return 0, // invalid or 4-byte sequence: relative
|
|
||||||
};
|
|
||||||
if b.len() < colon_i + 1 || b[colon_i] != b':' {
|
|
||||||
return 0; // relative
|
|
||||||
}
|
|
||||||
if b.len() > colon_i + 1 && sep(b[colon_i + 1]) {
|
|
||||||
colon_i + 2 // drive_absolute
|
|
||||||
} else {
|
|
||||||
colon_i + 1 // drive_relative
|
|
||||||
}
|
}
|
||||||
|
&path[start_index + 1..end_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open the given named theme. If there are any errors then messages
|
/// Open the given named theme. If there are any errors then messages
|
||||||
@@ -461,26 +405,12 @@ mod tests {
|
|||||||
("C:basename.ext", "basename.ext"),
|
("C:basename.ext", "basename.ext"),
|
||||||
("C:basename.ext\\", "basename.ext"),
|
("C:basename.ext\\", "basename.ext"),
|
||||||
("C:basename.ext\\\\", "basename.ext"),
|
("C:basename.ext\\\\", "basename.ext"),
|
||||||
("\\\\.", ""),
|
|
||||||
("\\\\.\\", ""),
|
|
||||||
("\\\\.\\basename.ext", "basename.ext"),
|
|
||||||
("\\\\?", ""),
|
|
||||||
("\\\\?\\", ""),
|
|
||||||
("\\\\?\\basename.ext", "basename.ext"),
|
|
||||||
("C:foo", "foo"),
|
("C:foo", "foo"),
|
||||||
("file:stream", "file:stream"),
|
("file:stream", "file:stream"),
|
||||||
// Non-letter drive designators are drive roots too (the
|
// Non-letter drive designators are drive roots too (the
|
||||||
// RV13 finding): `1:theme` is `theme`, not the whole name.
|
// RV13 finding): `1:theme` is `theme`, not the whole name.
|
||||||
("1:theme", "theme"),
|
("1:theme", "theme"),
|
||||||
("1:", ""),
|
("1:", ""),
|
||||||
("\u{3bb}:theme", "theme"),
|
|
||||||
("\u{20ac}:\\theme", "theme"),
|
|
||||||
// UNC roots.
|
|
||||||
("//a", ""),
|
|
||||||
("//a/b", ""),
|
|
||||||
("//a/b/c", "c"),
|
|
||||||
("\\\\server\\\\share\\file", "file"),
|
|
||||||
("//", ""),
|
|
||||||
] {
|
] {
|
||||||
assert_eq!(basename_windows(input), expected, "{input:?}");
|
assert_eq!(basename_windows(input), expected, "{input:?}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,16 +27,14 @@ channel, explicit value kept. `+show-config` for this tip build prints `tip` lik
|
|||||||
(Fixed a test-only pid-file collision between suites.)
|
(Fixed a test-only pid-file collision between suites.)
|
||||||
|
|
||||||
## 3. Windows zig_error
|
## 3. Windows zig_error
|
||||||
Installed Zig std is 0.16 (not 0.15.2) so the table follows 0.15.2 `windows.OpenFile` from knowledge of
|
/usr/lib/zig/std is 0.16; the real 0.15.2 std is at `target/zig-0.15.2/.../lib/std`. The table follows 0.15.2 `windows.OpenFile`'s NTSTATUS arms (verified), translated to Win32 codes (see the doc table in `os/zig_error.rs`): sharing violation,
|
||||||
its NTSTATUS arms, translated to Win32 codes (see the doc table in `os/zig_error.rs`): sharing violation,
|
|
||||||
access denied, user-mapped-file -> AccessDenied; invalid name -> BadPathName; BAD_NETPATH/BAD_NET_NAME ->
|
access denied, user-mapped-file -> AccessDenied; invalid name -> BadPathName; BAD_NETPATH/BAD_NET_NAME ->
|
||||||
NetworkNotFound; NOT_READY -> NoDevice; PIPE_BUSY -> PipeBusy; ALREADY_EXISTS -> PathAlreadyExists;
|
NetworkNotFound; NOT_READY -> NoDevice; PIPE_BUSY -> PipeBusy; ALREADY_EXISTS -> PathAlreadyExists;
|
||||||
ERROR_DIRECTORY -> NotDir; 206 -> NameTooLong. Removed CreateProcess-only entries (FileBusy, InvalidName,
|
ERROR_DIRECTORY -> NotDir; 206 -> NameTooLong. Removed CreateProcess-only entries (FileBusy, InvalidName,
|
||||||
InvalidExe, SystemResources, NoSpaceLeft) which file opens never produce (Unexpected). Table is a
|
InvalidExe, SystemResources, NoSpaceLeft) which file opens never produce (Unexpected). Table is a
|
||||||
host-independent fn so it is tested on Linux. Caveat: 0.16 retries sharing violations then FileBusy; the
|
host-independent fn so it is tested on Linux. (FILE_IS_A_DIRECTORY -> IsDir is unobservable: Win32 reports it as ERROR_ACCESS_DENIED.)
|
||||||
oracle is 0.15.2 behaviour (AccessDenied), as requested.
|
|
||||||
|
|
||||||
## 4. basename
|
## 4. basename
|
||||||
`config/theme.rs`: exact `ComponentIterator` + `parsePathWindows` port (`basename_windows`, `basename_posix`):
|
`config/theme.rs`: exact port of 0.15.2 `basenamePosix`/`basenameWindows` (a `:` at byte index 1 ends the drive designator
|
||||||
any 1-3 byte code point + `:` is a drive root (so `1:theme` -> `theme`), UNC/local-device roots. Tests
|
for any first byte, so `1:theme` -> `theme`). Tests include the 0.15.2 `testBasenameWindows` cases.
|
||||||
include all `testBasenameWindows` cases from Zig std.
|
(An earlier draft ported 0.16's ComponentIterator version; replaced.)
|
||||||
Reference in new issue
Block a user