From f8fe0445a5baca815d9dd4fc70b9b41011093fab Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 4 Jul 2024 15:08:29 -0700 Subject: [PATCH] core: clicking OSC8 links work --- src/Surface.zig | 40 +++++++++++++++++++++++++++++++++------- src/input/Link.zig | 4 ++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index bb90841f8..286de2625 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -2519,16 +2519,15 @@ fn clickMoveCursor(self: *Surface, to: terminal.Pin) !void { } /// Returns the link at the given cursor position, if any. +/// +/// Requires the renderer mutex is held. fn linkAtPos( self: *Surface, pos: apprt.CursorPos, ) !?struct { - DerivedConfig.Link, + input.Link.Action, terminal.Selection, } { - // If we have no configured links we can save a lot of work - if (self.config.links.len == 0) return null; - // Convert our cursor position to a screen point. const screen = &self.renderer_state.terminal.screen; const mouse_pin: terminal.Pin = mouse_pin: { @@ -2543,6 +2542,19 @@ fn linkAtPos( // Get our comparison mods const mouse_mods = self.mouseModsWithCapture(self.mouse.mods); + // If we have the proper modifiers set then we can check for OSC8 links. + if (mouse_mods.equal(input.ctrlOrSuper(.{}))) hyperlink: { + const rac = mouse_pin.rowAndCell(); + const cell = rac.cell; + if (!cell.hyperlink) break :hyperlink; + const sel = terminal.Selection.init(mouse_pin, mouse_pin, false); + return .{ ._open_osc8, sel }; + } + + // If we have no OSC8 links then we fallback to regex-based URL detection. + // If we have no configured links we can save a lot of work going forward. + if (self.config.links.len == 0) return null; + // Get the line we're hovering over. const line = screen.selectLine(.{ .pin = mouse_pin, @@ -2571,7 +2583,7 @@ fn linkAtPos( defer match.deinit(); const sel = match.selection(); if (!sel.contains(screen, mouse_pin)) continue; - return .{ link, sel }; + return .{ link.action, sel }; } } @@ -2602,8 +2614,8 @@ 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 link, const sel = try self.linkAtPos(pos) orelse return false; - switch (link.action) { + const action, const sel = try self.linkAtPos(pos) orelse return false; + switch (action) { .open => { const str = try self.io.terminal.screen.selectionString(self.alloc, .{ .sel = sel, @@ -2612,6 +2624,20 @@ fn processLinks(self: *Surface, pos: apprt.CursorPos) !bool { defer self.alloc.free(str); try internal_os.open(self.alloc, str); }, + + ._open_osc8 => { + // Note: we probably want to put this into a helper on page. + const pin = sel.start(); + const page = &pin.page.data; + const cell = pin.rowAndCell().cell; + const link_id = page.lookupHyperlink(cell) orelse { + log.warn("failed to find hyperlink for cell", .{}); + return false; + }; + const link = page.hyperlink_set.get(page.memory, link_id); + const uri = link.uri.offset.ptr(page.memory)[0..link.uri.len]; + try internal_os.open(self.alloc, uri); + }, } return true; diff --git a/src/input/Link.zig b/src/input/Link.zig index 86a9402d5..adc52a270 100644 --- a/src/input/Link.zig +++ b/src/input/Link.zig @@ -23,6 +23,10 @@ pub const Action = union(enum) { /// Open the full matched value using the default open program. /// For example, on macOS this is "open" and on Linux this is "xdg-open". open: void, + + /// Open the OSC8 hyperlink under the mouse position. _-prefixed means + /// this can't be user-specified, it's only used internally. + _open_osc8: void, }; pub const Highlight = union(enum) {