diff --git a/src/Surface.zig b/src/Surface.zig index eb0c178ec..f427d9071 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -854,7 +854,7 @@ fn modsChanged(self: *Surface, mods: input.Mods) void { { self.renderer_state.mutex.lock(); defer self.renderer_state.mutex.unlock(); - self.renderer_state.mouse.mods = mods; + self.renderer_state.mouse.mods = self.mouseModsWithCapture(self.mouse.mods); } self.queueRender() catch |err| { @@ -2372,6 +2372,9 @@ fn linkAtPos( break :mouse_pt viewport_point.toScreen(&self.io.terminal.screen); }; + // Get our comparison mods + const mouse_mods = self.mouseModsWithCapture(self.mouse.mods); + // Get the line we're hovering over. const line = self.io.terminal.screen.getLine(mouse_pt) orelse return null; @@ -2382,7 +2385,7 @@ fn linkAtPos( for (self.config.links) |link| { switch (link.highlight) { .always, .hover => {}, - .always_mods, .hover_mods => |v| if (!v.equal(self.mouse.mods)) continue, + .always_mods, .hover_mods => |v| if (!v.equal(mouse_mods)) continue, } var it = strmap.searchIterator(link.regex); @@ -2398,6 +2401,25 @@ fn linkAtPos( return null; } +/// 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. +/// +/// The renderer state mutex must be held. +fn mouseModsWithCapture(self: *Surface, mods: input.Mods) input.Mods { + // In any of these scenarios, whatever mods are set (even shift) + // are preserved. + if (self.io.terminal.flags.mouse_event == .none) return mods; + if (!mods.shift) return mods; + if (self.mouseShiftCapture(false)) return mods; + + // We have mouse capture, shift set, and we're not allowed to capture + // shift, so we can clear shift. + var final = mods; + final.shift = false; + return final; +} + /// Attempt to invoke the action of any link that is under the /// given position. /// diff --git a/src/input/Link.zig b/src/input/Link.zig index cdd87ef02..79ea0cbed 100644 --- a/src/input/Link.zig +++ b/src/input/Link.zig @@ -36,6 +36,11 @@ pub const Highlight = union(enum) { /// hovering or always. For always, all links will be highlighted /// when the mods are pressed regardless of if the mouse is hovering /// over them. + /// + /// Note that if "shift" is specified here, this will NEVER match in + /// TUI programs that capture mouse events. "Shift" with mouse capture + /// escapes the mouse capture but strips the "shift" so it can't be + /// detected. always_mods: Mods, hover_mods: Mods, }; diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 4a580ace9..a0fd8ea00 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1551,12 +1551,12 @@ fn rebuildCells( const arena_alloc = arena.allocator(); // Create our match set for the links. - var link_match_set = try self.config.links.matchSet( + var link_match_set: link.MatchSet = if (mouse.point) |mouse_pt| try self.config.links.matchSet( arena_alloc, screen, - mouse.point orelse .{}, + mouse_pt, mouse.mods, - ); + ) else .{}; // Determine our x/y range for preedit. We don't want to render anything // here because we will render the preedit separately. diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 34a326379..608f4fdcb 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -987,12 +987,12 @@ pub fn rebuildCells( self.gl_cells_written = 0; // Create our match set for the links. - var link_match_set = try self.config.links.matchSet( + var link_match_set: link.MatchSet = if (mouse.point) |mouse_pt| try self.config.links.matchSet( arena_alloc, screen, - mouse.point orelse .{}, + mouse_pt, mouse.mods, - ); + ) else .{}; // Determine our x/y range for preedit. We don't want to render anything // here because we will render the preedit separately. diff --git a/src/renderer/link.zig b/src/renderer/link.zig index 574d18e56..ee1532463 100644 --- a/src/renderer/link.zig +++ b/src/renderer/link.zig @@ -139,7 +139,7 @@ pub const MatchSet = struct { /// The matches. /// /// Important: this must be in left-to-right top-to-bottom order. - matches: []const terminal.Selection, + matches: []const terminal.Selection = &.{}, i: usize = 0, pub fn deinit(self: *MatchSet, alloc: Allocator) void {