aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/os/path.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/os/path.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/os/path.zig')
-rw-r--r--vendor/libghostty-vt/src/os/path.zig89
1 files changed, 89 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/os/path.zig b/vendor/libghostty-vt/src/os/path.zig
new file mode 100644
index 00000000..730ae692
--- /dev/null
+++ b/vendor/libghostty-vt/src/os/path.zig
@@ -0,0 +1,89 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const Allocator = std.mem.Allocator;
+const testing = std.testing;
+
+/// Search for "cmd" in the PATH and return the absolute path. This will
+/// always allocate if there is a non-null result. The caller must free the
+/// resulting value.
+pub fn expand(alloc: Allocator, cmd: []const u8) !?[]u8 {
+ // If the command already contains a slash, then we return it as-is
+ // because it is assumed to be absolute or relative.
+ if (std.mem.indexOfScalar(u8, cmd, '/') != null) {
+ return try alloc.dupe(u8, cmd);
+ }
+
+ const PATH = switch (builtin.os.tag) {
+ .windows => blk: {
+ const win_path = std.process.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse return null;
+ const path = try std.unicode.utf16LeToUtf8Alloc(alloc, win_path);
+ break :blk path;
+ },
+ else => std.posix.getenvZ("PATH") orelse return null,
+ };
+ defer if (builtin.os.tag == .windows) alloc.free(PATH);
+
+ var path_buf: [std.fs.max_path_bytes]u8 = undefined;
+ var it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter);
+ var seen_eacces = false;
+ while (it.next()) |search_path| {
+ // We need enough space in our path buffer to store this
+ const path_len = search_path.len + cmd.len + 1;
+ if (path_buf.len < path_len) return error.PathTooLong;
+
+ // Copy in the full path
+ @memcpy(path_buf[0..search_path.len], search_path);
+ path_buf[search_path.len] = std.fs.path.sep;
+ @memcpy(path_buf[search_path.len + 1 ..][0..cmd.len], cmd);
+ path_buf[path_len] = 0;
+ const full_path = path_buf[0..path_len :0];
+
+ // Stat it
+ const f = std.fs.cwd().openFile(
+ full_path,
+ .{},
+ ) catch |err| switch (err) {
+ error.FileNotFound => continue,
+ error.AccessDenied => {
+ // Accumulate this and return it later so we can try other
+ // paths that we have access to.
+ seen_eacces = true;
+ continue;
+ },
+ else => return err,
+ };
+ defer f.close();
+ const stat = try f.stat();
+ if (stat.kind != .directory and isExecutable(stat.mode)) {
+ return try alloc.dupe(u8, full_path);
+ }
+ }
+
+ if (seen_eacces) return error.AccessDenied;
+
+ return null;
+}
+
+fn isExecutable(mode: std.fs.File.Mode) bool {
+ if (builtin.os.tag == .windows) return true;
+ return mode & 0o0111 != 0;
+}
+
+// `uname -n` is the *nix equivalent of `hostname.exe` on Windows
+test "expand: hostname" {
+ const executable = if (builtin.os.tag == .windows) "hostname.exe" else "uname";
+ const path = (try expand(testing.allocator, executable)).?;
+ defer testing.allocator.free(path);
+ try testing.expect(path.len > executable.len);
+}
+
+test "expand: does not exist" {
+ const path = try expand(testing.allocator, "thisreallyprobablydoesntexist123");
+ try testing.expect(path == null);
+}
+
+test "expand: slash" {
+ const path = (try expand(testing.allocator, "foo/env")).?;
+ defer testing.allocator.free(path);
+ try testing.expect(path.len == 7);
+}