mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
feat: expand tildes ~
in config file paths to HOME
(#3811)
Fixes https://github.com/ghostty-org/ghostty/issues/3328
This commit is contained in:
@ -4361,6 +4361,45 @@ pub const RepeatablePath = struct {
|
|||||||
// If it isn't absolute, we need to make it absolute relative
|
// If it isn't absolute, we need to make it absolute relative
|
||||||
// to the base.
|
// to the base.
|
||||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
|
|
||||||
|
// Check if the path starts with a tilde and expand it to the
|
||||||
|
// home directory on Linux/macOS. We explicitly look for "~/"
|
||||||
|
// because we don't support alternate users such as "~alice/"
|
||||||
|
if (std.mem.startsWith(u8, path, "~/")) expand: {
|
||||||
|
// Windows isn't supported yet
|
||||||
|
if (comptime builtin.os.tag == .windows) break :expand;
|
||||||
|
|
||||||
|
const expanded: []const u8 = internal_os.expandHome(
|
||||||
|
path,
|
||||||
|
&buf,
|
||||||
|
) catch |err| {
|
||||||
|
try diags.append(alloc, .{
|
||||||
|
.message = try std.fmt.allocPrintZ(
|
||||||
|
alloc,
|
||||||
|
"error expanding home directory for path {s}: {}",
|
||||||
|
.{ path, err },
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Blank this path so that we don't attempt to resolve it
|
||||||
|
// again
|
||||||
|
self.value.items[i] = .{ .required = "" };
|
||||||
|
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
"expanding file path from home directory: path={s}",
|
||||||
|
.{expanded},
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (self.value.items[i]) {
|
||||||
|
.optional, .required => |*p| p.* = try alloc.dupeZ(u8, expanded),
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const abs = dir.realpath(path, &buf) catch |err| abs: {
|
const abs = dir.realpath(path, &buf) catch |err| abs: {
|
||||||
if (err == error.FileNotFound) {
|
if (err == error.FileNotFound) {
|
||||||
// The file doesn't exist. Try to resolve the relative path
|
// The file doesn't exist. Try to resolve the relative path
|
||||||
|
@ -12,7 +12,7 @@ const Error = error{
|
|||||||
|
|
||||||
/// Determine the home directory for the currently executing user. This
|
/// Determine the home directory for the currently executing user. This
|
||||||
/// is generally an expensive process so the value should be cached.
|
/// is generally an expensive process so the value should be cached.
|
||||||
pub inline fn home(buf: []u8) !?[]u8 {
|
pub inline fn home(buf: []u8) !?[]const u8 {
|
||||||
return switch (builtin.os.tag) {
|
return switch (builtin.os.tag) {
|
||||||
inline .linux, .macos => try homeUnix(buf),
|
inline .linux, .macos => try homeUnix(buf),
|
||||||
.windows => try homeWindows(buf),
|
.windows => try homeWindows(buf),
|
||||||
@ -24,7 +24,7 @@ pub inline fn home(buf: []u8) !?[]u8 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn homeUnix(buf: []u8) !?[]u8 {
|
fn homeUnix(buf: []u8) !?[]const u8 {
|
||||||
// First: if we have a HOME env var, then we use that.
|
// First: if we have a HOME env var, then we use that.
|
||||||
if (posix.getenv("HOME")) |result| {
|
if (posix.getenv("HOME")) |result| {
|
||||||
if (buf.len < result.len) return Error.BufferTooSmall;
|
if (buf.len < result.len) return Error.BufferTooSmall;
|
||||||
@ -77,7 +77,7 @@ fn homeUnix(buf: []u8) !?[]u8 {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn homeWindows(buf: []u8) !?[]u8 {
|
fn homeWindows(buf: []u8) !?[]const u8 {
|
||||||
const drive_len = blk: {
|
const drive_len = blk: {
|
||||||
var fba_instance = std.heap.FixedBufferAllocator.init(buf);
|
var fba_instance = std.heap.FixedBufferAllocator.init(buf);
|
||||||
const fba = fba_instance.allocator();
|
const fba = fba_instance.allocator();
|
||||||
@ -110,6 +110,68 @@ fn trimSpace(input: []const u8) []const u8 {
|
|||||||
return std.mem.trim(u8, input, " \n\t");
|
return std.mem.trim(u8, input, " \n\t");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const ExpandError = error{
|
||||||
|
HomeDetectionFailed,
|
||||||
|
BufferTooSmall,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Expands a path that starts with a tilde (~) to the home directory of
|
||||||
|
/// the current user.
|
||||||
|
///
|
||||||
|
/// Errors if `home` fails or if the size of the expanded path is larger
|
||||||
|
/// than `buf.len`.
|
||||||
|
pub fn expandHome(path: []const u8, buf: []u8) ExpandError![]const u8 {
|
||||||
|
return switch (builtin.os.tag) {
|
||||||
|
.linux, .macos => try expandHomeUnix(path, buf),
|
||||||
|
.ios => return path,
|
||||||
|
else => @compileError("unimplemented"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expandHomeUnix(path: []const u8, buf: []u8) ExpandError![]const u8 {
|
||||||
|
if (!std.mem.startsWith(u8, path, "~/")) return path;
|
||||||
|
const home_dir: []const u8 = if (home(buf)) |home_|
|
||||||
|
home_ orelse return error.HomeDetectionFailed
|
||||||
|
else |_|
|
||||||
|
return error.HomeDetectionFailed;
|
||||||
|
const rest = path[1..]; // Skip the ~
|
||||||
|
const expanded_len = home_dir.len + rest.len;
|
||||||
|
|
||||||
|
if (expanded_len > buf.len) return Error.BufferTooSmall;
|
||||||
|
@memcpy(buf[home_dir.len..expanded_len], rest);
|
||||||
|
|
||||||
|
return buf[0..expanded_len];
|
||||||
|
}
|
||||||
|
|
||||||
|
test "expandHomeUnix" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const allocator = testing.allocator;
|
||||||
|
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||||
|
const home_dir = try expandHomeUnix("~/", &buf);
|
||||||
|
// Joining the home directory `~` with the path `/`
|
||||||
|
// the result should end with a separator here. (e.g. `/home/user/`)
|
||||||
|
try testing.expect(home_dir[home_dir.len - 1] == std.fs.path.sep);
|
||||||
|
|
||||||
|
const downloads = try expandHomeUnix("~/Downloads/shader.glsl", &buf);
|
||||||
|
const expected_downloads = try std.mem.concat(allocator, u8, &[_][]const u8{ home_dir, "Downloads/shader.glsl" });
|
||||||
|
defer allocator.free(expected_downloads);
|
||||||
|
try testing.expectEqualStrings(expected_downloads, downloads);
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("~", try expandHomeUnix("~", &buf));
|
||||||
|
try testing.expectEqualStrings("~abc/", try expandHomeUnix("~abc/", &buf));
|
||||||
|
try testing.expectEqualStrings("/home/user", try expandHomeUnix("/home/user", &buf));
|
||||||
|
try testing.expectEqualStrings("", try expandHomeUnix("", &buf));
|
||||||
|
|
||||||
|
// Expect an error if the buffer is large enough to hold the home directory,
|
||||||
|
// but not the expanded path
|
||||||
|
var small_buf = try allocator.alloc(u8, home_dir.len);
|
||||||
|
defer allocator.free(small_buf);
|
||||||
|
try testing.expectError(error.BufferTooSmall, expandHomeUnix(
|
||||||
|
"~/Downloads",
|
||||||
|
small_buf[0..],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ pub const freeTmpDir = file.freeTmpDir;
|
|||||||
pub const isFlatpak = flatpak.isFlatpak;
|
pub const isFlatpak = flatpak.isFlatpak;
|
||||||
pub const FlatpakHostCommand = flatpak.FlatpakHostCommand;
|
pub const FlatpakHostCommand = flatpak.FlatpakHostCommand;
|
||||||
pub const home = homedir.home;
|
pub const home = homedir.home;
|
||||||
|
pub const expandHome = homedir.expandHome;
|
||||||
pub const ensureLocale = locale.ensureLocale;
|
pub const ensureLocale = locale.ensureLocale;
|
||||||
pub const clickInterval = mouse.clickInterval;
|
pub const clickInterval = mouse.clickInterval;
|
||||||
pub const open = openpkg.open;
|
pub const open = openpkg.open;
|
||||||
|
Reference in New Issue
Block a user