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,
|
.trim = false,
|
||||||
});
|
});
|
||||||
defer self.alloc.free(str);
|
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 => {
|
._open_osc8 => {
|
||||||
|
@ -26,7 +26,7 @@ pub const regex =
|
|||||||
"(?:" ++ url_schemes ++
|
"(?:" ++ url_schemes ++
|
||||||
\\)(?:
|
\\)(?:
|
||||||
++ ipv6_url_pattern ++
|
++ ipv6_url_pattern ++
|
||||||
\\|[\w\-.~:/?#@!$&*+,;=%]+(?:[\(\[]\w*[\)\]])?)+(?<![,.])|(?:\.\.\/|\.\/*|\/)[\w\-.~:\/?#@!$&*+,;=%]+(?:\/[\w\-.~:\/?#@!$&*+,;=%]*)*
|
\\|[\w\-.~:/?#@!$&*+,;=%]+(?:[\(\[]\w*[\)\]])?)+(?<![,.])|(?:~|~/|\.\.\/|\.\/*|\/)[\w\-.~:\/?#@!$&*+,;=%]*(?:\/[\w\-.~:\/?#@!$&*+,;=%]*)*
|
||||||
;
|
;
|
||||||
const url_schemes =
|
const url_schemes =
|
||||||
\\https?://|mailto:|ftp://|file:|ssh:|git://|ssh://|tel:|magnet:|ipfs://|ipns://|gemini://|gopher://|news:
|
\\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)",
|
.input = "[link](/home/user/ghostty.user/example)",
|
||||||
.expect = "/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
|
// IPv6 URL tests - Basic tests
|
||||||
.{
|
.{
|
||||||
.input = "Serving HTTP on :: port 8000 (http://[::]:8000/)",
|
.input = "Serving HTTP on :: port 8000 (http://[::]:8000/)",
|
||||||
|
@ -29,6 +29,29 @@ pub const Link = struct {
|
|||||||
pub const Set = struct {
|
pub const Set = struct {
|
||||||
links: []Link,
|
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.
|
/// Returns the slice of links from the configuration.
|
||||||
pub fn fromConfig(
|
pub fn fromConfig(
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
@ -112,7 +135,6 @@ pub const Set = struct {
|
|||||||
mouse_pin: terminal.Pin,
|
mouse_pin: terminal.Pin,
|
||||||
mouse_mods: inputpkg.Mods,
|
mouse_mods: inputpkg.Mods,
|
||||||
) !void {
|
) !void {
|
||||||
_ = alloc;
|
|
||||||
|
|
||||||
// If the right mods aren't pressed, then we can't match.
|
// If the right mods aren't pressed, then we can't match.
|
||||||
if (!mouse_mods.equal(inputpkg.ctrlOrSuper(.{}))) return;
|
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);
|
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)
|
// If our link has an implicit ID (no ID set explicitly via OSC8)
|
||||||
// then we use an alternate matching technique that iterates forward
|
// then we use an alternate matching technique that iterates forward
|
||||||
// and backward until it finds boundaries.
|
// and backward until it finds boundaries.
|
||||||
if (link.id == .implicit) {
|
if (link.id == .implicit) {
|
||||||
const uri = link.uri.offset.ptr(page.memory)[0..link.uri.len];
|
|
||||||
return try self.matchSetFromOSC8Implicit(
|
return try self.matchSetFromOSC8Implicit(
|
||||||
|
alloc,
|
||||||
matches,
|
matches,
|
||||||
mouse_pin,
|
mouse_pin,
|
||||||
uri,
|
absoluteUri,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,6 +228,7 @@ pub const Set = struct {
|
|||||||
/// around the mouse pin.
|
/// around the mouse pin.
|
||||||
fn matchSetFromOSC8Implicit(
|
fn matchSetFromOSC8Implicit(
|
||||||
self: *const Set,
|
self: *const Set,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
matches: *std.ArrayList(terminal.Selection),
|
matches: *std.ArrayList(terminal.Selection),
|
||||||
mouse_pin: terminal.Pin,
|
mouse_pin: terminal.Pin,
|
||||||
uri: []const u8,
|
uri: []const u8,
|
||||||
@ -231,9 +257,12 @@ pub const Set = struct {
|
|||||||
// If this link has an explicit ID then we found a boundary
|
// If this link has an explicit ID then we found a boundary
|
||||||
if (link.id != .implicit) break;
|
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];
|
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;
|
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 this link has an explicit ID then we found a boundary
|
||||||
if (link.id != .implicit) break;
|
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];
|
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;
|
sel.endPtr().* = cell_pin;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user