diff --git a/src/Surface.zig b/src/Surface.zig index 70c32098f..06701b536 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1053,10 +1053,10 @@ fn mouseRefreshLinks( .pointer, ); - switch (link[0]) { + switch (link.action) { .open => { const str = try self.io.terminal.screen.selectionString(self.alloc, .{ - .sel = link[1], + .sel = link.selection, .trim = false, }); defer self.alloc.free(str); @@ -1069,7 +1069,7 @@ fn mouseRefreshLinks( ._open_osc8 => link: { // Show the URL in the status bar - const pin = link[1].start(); + const pin = link.selection.start(); const uri = self.osc8URI(pin) orelse { log.warn("failed to get URI for OSC8 hyperlink", .{}); break :link; @@ -3149,16 +3149,18 @@ fn clickMoveCursor(self: *Surface, to: terminal.Pin) !void { } } +const ActionSelection = struct { + action: input.Link.Action, + selection: terminal.Selection, +}; + /// Returns the link at the given cursor position, if any. /// /// Requires the renderer mutex is held. fn linkAtPos( self: *Surface, pos: apprt.CursorPos, -) !?struct { - input.Link.Action, - terminal.Selection, -} { +) !?ActionSelection { // Convert our cursor position to a screen point. const screen = &self.renderer_state.terminal.screen; const mouse_pin: terminal.Pin = mouse_pin: { @@ -3179,7 +3181,7 @@ fn linkAtPos( const cell = rac.cell; if (!cell.hyperlink) break :hyperlink; const sel = terminal.Selection.init(mouse_pin, mouse_pin, false); - return .{ ._open_osc8, sel }; + return .{ .action = ._open_osc8, .selection = sel }; } // If we have no OSC8 links then we fallback to regex-based URL detection. @@ -3214,13 +3216,60 @@ fn linkAtPos( defer match.deinit(); const sel = match.selection(); if (!sel.contains(screen, mouse_pin)) continue; - return .{ link.action, sel }; + return .{ .action = link.action, .selection = sel }; + } + } + + // this is the last chance to return any substr as clickable or not + // by checking if any given str is path resolvable or not. below lines of code + // will work only for file paths. this covers both relative and absolute paths. + // some of the paths may not work it is based on the `std.fs.realpath` + const cwd = self.io.terminal.getPwd(); + var split_str = std.mem.splitSequence(u8, strmap.string, " "); + while (split_str.next()) |potential_path| { + const path_slice: []const []const u8 = &[_][]const u8{ + cwd.?, + potential_path, + }; + const cwd_path = try std.fs.path.join(self.alloc, path_slice); + defer self.alloc.free(cwd_path); + + // we can skip opening the current path as we're already at the right path + if (std.mem.eql(u8, cwd.?, cwd_path)) { + continue; + } + + var buf: [std.fs.max_path_bytes]u8 = undefined; + const real_path = std.fs.realpath(cwd_path, &buf) catch null; + + if (real_path != null) { + const region = findSubstringIndices(strmap.string, potential_path); + if (region) |r| { + const start_idx = r[0]; + const end_idx = r[1]; + const start_pt = strmap.map[start_idx]; + const end_pt = strmap.map[end_idx + 1]; + const sel = screen.selectWordBetween(start_pt, end_pt); + if (sel) |s| { + const result = .{ + .action = input.Link.Action{ .open = {} }, + .selection = s, + }; + return result; + } + } } } return null; } +fn findSubstringIndices(haystack: []const u8, needle: []const u8) ?[2]usize { + const start_index = std.mem.indexOf(u8, haystack, needle) orelse return null; + const end_index = start_index + needle.len; + return .{ start_index, end_index }; +} + /// This returns the mouse mods to consider for link highlighting or /// other purposes taking into account when shift is pressed for releasing /// the mouse from capture. @@ -3245,7 +3294,9 @@ fn mouseModsWithCapture(self: *Surface, mods: input.Mods) input.Mods { /// /// Requires the renderer state mutex is held. fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool { - const action, const sel = try self.linkAtPos(pos) orelse return false; + const link = try self.linkAtPos(pos) orelse return false; + const action = link.action; + const sel = link.selection; switch (action) { .open => { const str = try self.io.terminal.screen.selectionString(self.alloc, .{ diff --git a/src/config/url.zig b/src/config/url.zig index 78f9816fd..dcc1902e6 100644 --- a/src/config/url.zig +++ b/src/config/url.zig @@ -26,7 +26,7 @@ pub const regex = "(?:" ++ url_schemes ++ \\)(?: ++ ipv6_url_pattern ++ - \\|[\w\-.~:/?#@!$&*+,;=%]+(?:[\(\[]\w*[\)\]])?)+(?