mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Fix clickable tilde paths in terminal
- Improve path handling in Surface.zig to correctly expand standalone tilde (~) to $HOME - Update URL regex in config/url.zig This change ensures that: - Standalone tilde (~) is clickable and opens the home directory - Paths starting with ~/ correctly expand to the home directory - Other path patterns remain unchanged Add test cases for tilde path handling in url.zig
This commit is contained in:
@ -3275,7 +3275,21 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool {
|
||||
.trim = false,
|
||||
});
|
||||
defer self.alloc.free(str);
|
||||
try internal_os.open(self.alloc, .unknown, str);
|
||||
|
||||
// Handle paths starting with ~
|
||||
if (str.len > 0 and str[0] == '~') {
|
||||
const home = std.posix.getenv("HOME") orelse "";
|
||||
const path = if (str.len == 1)
|
||||
try std.fmt.allocPrint(self.alloc, "{s}", .{home})
|
||||
else if (str[1] == '/')
|
||||
try std.fmt.allocPrint(self.alloc, "{s}{s}", .{ home, str[1..] })
|
||||
else
|
||||
try std.fmt.allocPrint(self.alloc, "{s}", .{str});
|
||||
defer self.alloc.free(path);
|
||||
try internal_os.open(self.alloc, .unknown, path);
|
||||
} else {
|
||||
try internal_os.open(self.alloc, .unknown, str);
|
||||
}
|
||||
},
|
||||
|
||||
._open_osc8 => {
|
||||
|
@ -26,7 +26,7 @@ pub const regex =
|
||||
"(?:" ++ url_schemes ++
|
||||
\\)(?:
|
||||
++ ipv6_url_pattern ++
|
||||
\\|[\w\-.~:/?#@!$&*+,;=%]+(?:[\(\[]\w*[\)\]])?)+(?<![,.])|(?:\.\.\/|\.\/*|\/)[\w\-.~:\/?#@!$&*+,;=%]+(?:\/[\w\-.~:\/?#@!$&*+,;=%]*)*
|
||||
\\|[\w\-.~:/?#@!$&*+,;=%]+(?:[\(\[]\w*[\)\]])?)+(?<![,.])|(?:~|~/|\.\.\/|\.\/*|\/)[\w\-.~:\/?#@!$&*+,;=%]*(?:\/[\w\-.~:\/?#@!$&*+,;=%]*)*
|
||||
;
|
||||
const url_schemes =
|
||||
\\https?://|mailto:|ftp://|file:|ssh:|git://|ssh://|tel:|magnet:|ipfs://|ipns://|gemini://|gopher://|news:
|
||||
@ -200,6 +200,21 @@ test "url regex" {
|
||||
.input = "[link](/home/user/ghostty.user/example)",
|
||||
.expect = "/home/user/ghostty.user/example",
|
||||
},
|
||||
// Test standalone tilde
|
||||
.{
|
||||
.input = "~",
|
||||
.expect = "~",
|
||||
},
|
||||
// Test tilde with path
|
||||
.{
|
||||
.input = "~/Documents/example.txt",
|
||||
.expect = "~/Documents/example.txt",
|
||||
},
|
||||
// Test tilde in text context
|
||||
.{
|
||||
.input = "cd ~ to go home",
|
||||
.expect = "~",
|
||||
},
|
||||
// IPv6 URL tests - Basic tests
|
||||
.{
|
||||
.input = "Serving HTTP on :: port 8000 (http://[::]:8000/)",
|
||||
|
@ -29,6 +29,29 @@ pub const Link = struct {
|
||||
pub const Set = struct {
|
||||
links: []Link,
|
||||
|
||||
// Converts paths starting with `~` to absolute paths using the HOME environment variable
|
||||
fn interpretHomeDirectory(alloc: std.mem.Allocator, path: []const u8) ![]const u8 {
|
||||
// If the path starts with the project directory and contains a tilde,
|
||||
// we should only process the part after the tilde
|
||||
if (std.mem.indexOf(u8, path, "/~")) |tilde_pos| {
|
||||
const home = std.posix.getenv("HOME") orelse "";
|
||||
const after_tilde = path[tilde_pos + 2 ..];
|
||||
// Construct the path using just the home directory and the part after ~/
|
||||
const result = try std.fmt.allocPrint(alloc, "{s}/{s}", .{ home, after_tilde });
|
||||
return result;
|
||||
} else if (path.len > 0 and path[0] == '~') {
|
||||
const home = std.posix.getenv("HOME") orelse "";
|
||||
if (path.len > 1 and path[1] == '/') {
|
||||
const result = try std.fmt.allocPrint(alloc, "{s}{s}", .{ home, path[1..] });
|
||||
return result;
|
||||
} else if (path.len == 1) {
|
||||
const result = try std.fmt.allocPrint(alloc, "{s}", .{home});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/// Returns the slice of links from the configuration.
|
||||
pub fn fromConfig(
|
||||
alloc: Allocator,
|
||||
@ -112,7 +135,6 @@ pub const Set = struct {
|
||||
mouse_pin: terminal.Pin,
|
||||
mouse_mods: inputpkg.Mods,
|
||||
) !void {
|
||||
_ = alloc;
|
||||
|
||||
// If the right mods aren't pressed, then we can't match.
|
||||
if (!mouse_mods.equal(inputpkg.ctrlOrSuper(.{}))) return;
|
||||
@ -129,15 +151,18 @@ pub const Set = struct {
|
||||
};
|
||||
const link = page.hyperlink_set.get(page.memory, link_id);
|
||||
|
||||
// Convert paths starting with `~` to absolute paths
|
||||
const absoluteUri = try interpretHomeDirectory(alloc, link.uri.offset.ptr(page.memory)[0..link.uri.len]);
|
||||
|
||||
// If our link has an implicit ID (no ID set explicitly via OSC8)
|
||||
// then we use an alternate matching technique that iterates forward
|
||||
// and backward until it finds boundaries.
|
||||
if (link.id == .implicit) {
|
||||
const uri = link.uri.offset.ptr(page.memory)[0..link.uri.len];
|
||||
return try self.matchSetFromOSC8Implicit(
|
||||
alloc,
|
||||
matches,
|
||||
mouse_pin,
|
||||
uri,
|
||||
absoluteUri,
|
||||
);
|
||||
}
|
||||
|
||||
@ -203,6 +228,7 @@ pub const Set = struct {
|
||||
/// around the mouse pin.
|
||||
fn matchSetFromOSC8Implicit(
|
||||
self: *const Set,
|
||||
alloc: std.mem.Allocator,
|
||||
matches: *std.ArrayList(terminal.Selection),
|
||||
mouse_pin: terminal.Pin,
|
||||
uri: []const u8,
|
||||
@ -231,9 +257,12 @@ pub const Set = struct {
|
||||
// If this link has an explicit ID then we found a boundary
|
||||
if (link.id != .implicit) break;
|
||||
|
||||
// If this link has a different URI then we found a boundary
|
||||
// Convert paths starting with `~` to absolute paths
|
||||
const cell_uri = link.uri.offset.ptr(page.memory)[0..link.uri.len];
|
||||
if (!std.mem.eql(u8, uri, cell_uri)) break;
|
||||
const absoluteCellUri = try interpretHomeDirectory(alloc, cell_uri);
|
||||
|
||||
// If this link has a different URI then we found a boundary
|
||||
if (!std.mem.eql(u8, uri, absoluteCellUri)) break;
|
||||
|
||||
sel.startPtr().* = cell_pin;
|
||||
}
|
||||
@ -257,9 +286,12 @@ pub const Set = struct {
|
||||
// If this link has an explicit ID then we found a boundary
|
||||
if (link.id != .implicit) break;
|
||||
|
||||
// If this link has a different URI then we found a boundary
|
||||
// Convert paths starting with `~` to absolute paths
|
||||
const cell_uri = link.uri.offset.ptr(page.memory)[0..link.uri.len];
|
||||
if (!std.mem.eql(u8, uri, cell_uri)) break;
|
||||
const absoluteCellUri = try interpretHomeDirectory(alloc, cell_uri);
|
||||
|
||||
// If this link has a different URI then we found a boundary
|
||||
if (!std.mem.eql(u8, uri, absoluteCellUri)) break;
|
||||
|
||||
sel.endPtr().* = cell_pin;
|
||||
}
|
||||
|
Reference in New Issue
Block a user