diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/os/path.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (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.zig | 89 |
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); +} |
