Something went wrong. Try again.
A GPU terminal emulator for Wayland and Windows on libghostty-vt, drawing with Vulkan, running a program on a pty or a serial port.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! A pane's location: the directory its program is in, and the machine,//! when that is not this one.//!//! Only a shell can say where it is on another machine, and it says so with//! OSC 7: `file://host/path`, percent-encoded, or kitty's//! `kitty-shell-cwd://host/path`, which is not. Over ssh harrier sees only//! the ssh on this machine, so the host is whatever the shell at the far//! end reports, and nothing is guessed from a command line.//!//! Written as a person reads one: on this machine, the path with the home//! directory as `~`; on another, `host:path`, as scp has it, the path as//! the shell sent it, since nothing here knows that machine's home.
const std = @import("std");const builtin = @import("builtin");const Allocator = std.mem.Allocator;
const links = @import("links.zig");
/// Where a pane's program is. Both strings are owned.pub const Location = struct { /// The machine, when it is not this one; empty when it is. host: []u8, /// The directory, written as that machine's OS writes it. path: []u8,
pub fn deinit(l: *Location, gpa: Allocator) void { gpa.free(l.host); gpa.free(l.path); l.* = undefined; }
pub fn eql(a: Location, b: Location) bool { return std.mem.eql(u8, a.host, b.host) and std.mem.eql(u8, a.path, b.path); }
/// Where `url`, an OSC 7 report, says the shell is; null for a report /// that names no directory. `hostname` is this machine's. pub fn ofUrl(gpa: Allocator, url: []const u8, hostname: []const u8) Allocator.Error!?Location { var scratch: [std.fs.max_path_bytes]u8 = undefined; const parts = split(url, &scratch) orelse return null; const local = links.isLocalHost(parts.host, hostname); const host = try gpa.dupe(u8, if (local) "" else parts.host); errdefer gpa.free(host); // Another machine's path is as it sent it: it may not be this OS's. const path = if (local) try nativePath(gpa, parts.path) else try gpa.dupe(u8, parts.path); return .{ .host = host, .path = path }; }
/// A directory on this machine, as `/proc` finds it. pub fn ofPath(gpa: Allocator, path: []const u8) Allocator.Error!Location { const host = try gpa.dupe(u8, ""); errdefer gpa.free(host); return .{ .host = host, .path = try gpa.dupe(u8, path) }; }};
/// An OSC 7 report's host and path, the path's escapes decoded into/// `scratch` when the scheme has them.pub fn split(url: []const u8, scratch: []u8) ?struct { host: []const u8, path: []const u8 } { const encoded = for ([_][]const u8{ "file://", "kitty-shell-cwd://" }) |scheme| { if (std.ascii.startsWithIgnoreCase(url, scheme)) break std.mem.eql(u8, scheme, "file://"); } else return null; const rest = url[std.mem.findScalar(u8, url, ':').? + 3 ..]; const slash = std.mem.findScalar(u8, rest, '/') orelse return null; const host = rest[0..slash]; if (!encoded) return .{ .host = host, .path = rest[slash..] }; // Decoding only makes it shorter. if (rest.len - slash > scratch.len) return null; const raw = scratch[0 .. rest.len - slash]; @memcpy(raw, rest[slash..]); return .{ .host = host, .path = std.Uri.percentDecodeInPlace(raw) };}
/// A URL's path as this OS writes one, in an allocation: on Windows,/// `/C:/Users/me` is `C:\Users\me`.pub fn nativePath(gpa: Allocator, path: []const u8) Allocator.Error![]u8 { if (builtin.os.tag != .windows) return gpa.dupe(u8, path); const drive = path.len >= 3 and path[0] == '/' and std.ascii.isAlphabetic(path[1]) and path[2] == ':'; const out = try gpa.dupe(u8, if (drive) path[1..] else path); for (out) |*c| if (c.* == '/') { c.* = '\\'; }; return out;}
/// The home directory a location on this machine is written `~` under:/// `HOME`, or on Windows `USERPROFILE`.pub fn homeOf(env: *const std.process.Environ.Map) ?[]const u8 { return env.get(if (builtin.os.tag == .windows) "USERPROFILE" else "HOME");}
/// `l` as a person reads it, in `buf`, cut short if `buf` is: `host:path`/// for another machine, and on this one the path, with `home` at its start/// written `~`.pub fn format(buf: []u8, l: Location, home: ?[]const u8) []const u8 { var w: std.Io.Writer = .fixed(buf); if (l.host.len > 0) { w.print("{s}:{s}", .{ l.host, l.path }) catch {}; return w.buffered(); } const rest = if (home) |h| underHome(l.path, h) else null; if (rest) |r| w.print("~{s}", .{r}) catch {} else w.writeAll(l.path) catch {}; return w.buffered();}
/// What follows `home` in `path`, when `path` is in it; empty for `home`/// itself.fn underHome(path: []const u8, home: []const u8) ?[]const u8 { const h = std.mem.trimEnd(u8, home, "/\\"); if (h.len == 0) return null; const windows = builtin.os.tag == .windows; const starts = if (windows) std.ascii.startsWithIgnoreCase(path, h) else std.mem.startsWith(u8, path, h); if (!starts) return null; const rest = path[h.len..]; if (rest.len > 0 and rest[0] != '/' and !(windows and rest[0] == '\\')) return null; return rest;}
// ---------------------------------------------------------------------------
const testing = std.testing;
test "a report from this machine is a path, and from another a host too" { const a = testing.allocator; const windows = builtin.os.tag == .windows; var here = (try Location.ofUrl(a, "file://box/home/me/My%20Files", "box")).?; defer here.deinit(a); try testing.expectEqualStrings("", here.host); try testing.expectEqualStrings(if (windows) "\\home\\me\\My Files" else "/home/me/My Files", here.path); var none = (try Location.ofUrl(a, "file:///tmp", "box")).?; defer none.deinit(a); try testing.expectEqualStrings("", none.host); var named = (try Location.ofUrl(a, "file://LOCALHOST/tmp", "box")).?; defer named.deinit(a); try testing.expectEqualStrings("", named.host); var there = (try Location.ofUrl(a, "kitty-shell-cwd://router/etc/a b", "box")).?; defer there.deinit(a); try testing.expectEqualStrings("router", there.host); // Another machine's path is not made this OS's. try testing.expectEqualStrings("/etc/a b", there.path); try testing.expect(try Location.ofUrl(a, "http://router/x", "box") == null); try testing.expect(try Location.ofUrl(a, "file://router", "box") == null);}
test "a location as it is shown" { var buf: [128]u8 = undefined; const windows = builtin.os.tag == .windows; var host: [0]u8 = .{}; var p1 = "/home/me/dev/harrier".*; try testing.expectEqualStrings("~/dev/harrier", format(&buf, .{ .host = &host, .path = &p1 }, "/home/me")); var p2 = "/home/me".*; try testing.expectEqualStrings("~", format(&buf, .{ .host = &host, .path = &p2 }, "/home/me/")); var p3 = "/home/meadow".*; try testing.expectEqualStrings("/home/meadow", format(&buf, .{ .host = &host, .path = &p3 }, "/home/me")); try testing.expectEqualStrings("/home/meadow", format(&buf, .{ .host = &host, .path = &p3 }, null)); // Another machine's home is not known, so not written `~`. var router = "router".*; var p4 = "/home/me/etc".*; try testing.expectEqualStrings("router:/home/me/etc", format(&buf, .{ .host = &router, .path = &p4 }, "/home/me")); if (windows) { var p5 = "C:\\Users\\Me\\src".*; try testing.expectEqualStrings("~\\src", format(&buf, .{ .host = &host, .path = &p5 }, "c:\\users\\me")); }}