Repository navigation
fix: open terminal links on cmd-click even when mouse reporting is active #71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2785,13 +2785,11 @@ pub fn keyCallback( | |
| // Update our modifiers, this will update mouse mods too | ||
| self.modsChanged(event.mods); | ||
|
|
||
| // We only refresh links if | ||
| // 1. mouse reporting is off | ||
| // OR | ||
| // 2. mouse reporting is on and we are not reporting shift to the terminal | ||
| if (self.io.terminal.flags.mouse_event == .none or | ||
| (self.mouse.mods.shift and !self.mouseShiftCapture(false))) | ||
| { | ||
| // We refresh links when local link handling is allowed for the | ||
| // current mouse-reporting state and mods (see mouseLinkRefreshAllowed): | ||
| // mouse reporting off, shift releasing capture, or the ctrl/super link | ||
| // modifier held so Cmd-click opens links inside a mouse-grabbing TUI. | ||
| if (self.mouseLinkRefreshAllowed()) { | ||
| // Refresh our link state | ||
| const pos = self.rt_surface.getCursorPos() catch break :mouse_mods; | ||
| self.renderer_state.mutex.lock(); | ||
|
|
@@ -3813,6 +3811,35 @@ fn mouseShiftCapture(self: *const Surface, lock: bool) bool { | |
| }; | ||
| } | ||
|
|
||
| /// Returns true if link hover/highlight state should be evaluated locally | ||
| /// instead of handing the mouse event to the running program. | ||
| /// | ||
| /// We always evaluate links when mouse reporting is off. When a program has | ||
| /// mouse reporting enabled we still evaluate them if the user is holding | ||
| /// shift to release the mouse from capture, or is holding the ctrl/super | ||
| /// link-activation modifier. The latter lets Cmd-click (macOS) or Ctrl-click | ||
| /// open a link even while a fullscreen/alternate-screen TUI has grabbed the | ||
| /// mouse, matching iTerm2 and macOS Terminal. | ||
| fn mouseLinkRefreshAllowed(self: *const Surface) bool { | ||
| return mouseLinkRefreshAllowedState( | ||
| self.isMouseReporting(), | ||
| self.mouseShiftCapture(false), | ||
| self.mouse.mods, | ||
| ); | ||
| } | ||
|
|
||
| /// Pure decision logic for `mouseLinkRefreshAllowed`, split out so it can be | ||
| /// unit tested without constructing a `Surface`. | ||
| fn mouseLinkRefreshAllowedState( | ||
| mouse_reporting: bool, | ||
| shift_capture: bool, | ||
| mods: input.Mods, | ||
| ) bool { | ||
| if (!mouse_reporting) return true; | ||
| if (mods.shift and !shift_capture) return true; | ||
| return mods.equal(input.ctrlOrSuper(.{})); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is a design limitation that could be addressed by threading the configured |
||
| } | ||
|
|
||
| /// Returns true if the mouse is currently captured by the terminal | ||
| /// (i.e. reporting events). | ||
| pub fn mouseCaptured(self: *Surface) bool { | ||
|
|
@@ -4683,14 +4710,12 @@ pub fn cursorPosCallback( | |
| // 2. the cursor position has changed (either we have no previous state, or the state has | ||
| // changed) | ||
| // AND | ||
| // 1. mouse reporting is off | ||
| // OR | ||
| // 2. mouse reporting is on and we are not reporting shift to the terminal | ||
| // local link handling is allowed for the current mouse-reporting state | ||
| // and mods (see mouseLinkRefreshAllowed) | ||
| if ((over_link or | ||
| self.mouse.link_point == null or | ||
| (self.mouse.link_point != null and !self.mouse.link_point.?.eql(pos_vp))) and | ||
| (self.io.terminal.flags.mouse_event == .none or | ||
| (self.mouse.mods.shift and !self.mouseShiftCapture(false)))) | ||
| self.mouseLinkRefreshAllowed()) | ||
| { | ||
| // If we were previously over a link, we always update. We do this so that if the text | ||
| // changed underneath us, even if the mouse didn't move, we update the URL hints and state | ||
|
|
@@ -6531,6 +6556,39 @@ pub fn getProcessInfo(self: *Surface, comptime info: ProcessInfo) ?ProcessInfo.T | |
| return self.io.getProcessInfo(info); | ||
| } | ||
|
|
||
| test "Surface: mouseLinkRefreshAllowedState honors ctrl/super under mouse reporting" { | ||
| const ctrl_or_super = input.ctrlOrSuper(.{}); | ||
|
|
||
| // Mouse reporting off: links are always evaluated, regardless of mods. | ||
| try std.testing.expect(mouseLinkRefreshAllowedState(false, false, .{})); | ||
| try std.testing.expect(mouseLinkRefreshAllowedState(false, false, ctrl_or_super)); | ||
|
|
||
| // Mouse reporting on, no relevant mods: the event is reported to the app, | ||
| // links are not evaluated locally. | ||
| try std.testing.expect(!mouseLinkRefreshAllowedState(true, false, .{})); | ||
|
|
||
| // Mouse reporting on, ctrl/super link modifier held: links are evaluated | ||
| // so Cmd-click (macOS) / Ctrl-click opens a link even while a | ||
| // fullscreen/alternate-screen TUI has grabbed the mouse. This is the | ||
| // behavior that was missing in cmux issue #5128. | ||
| try std.testing.expect(mouseLinkRefreshAllowedState(true, false, ctrl_or_super)); | ||
|
|
||
| // Same as above but with shift-capture enabled: the ctrl/super link path | ||
| // must not be gated on shift_capture, since shift is not part of the chord. | ||
| try std.testing.expect(mouseLinkRefreshAllowedState(true, true, ctrl_or_super)); | ||
|
|
||
| // Mouse reporting on, shift held and shift-capture disallowed: evaluated | ||
| // (pre-existing shift-release-from-capture behavior, unchanged). | ||
| try std.testing.expect(mouseLinkRefreshAllowedState(true, false, .{ .shift = true })); | ||
|
|
||
| // Mouse reporting on, shift held but shift-capture allowed: reported. | ||
| try std.testing.expect(!mouseLinkRefreshAllowedState(true, true, .{ .shift = true })); | ||
|
|
||
| // Mouse reporting on, ctrl/super plus a non-shift modifier: not an exact | ||
| // link-activation chord, so the event is reported to the app. | ||
| try std.testing.expect(!mouseLinkRefreshAllowedState(true, false, input.ctrlOrSuper(.{ .alt = true }))); | ||
|
Comment on lines
+6562
to
+6589
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The truth table covers Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
| } | ||
|
|
||
| test "Surface: selection logic" { | ||
| // We disable format to make these easier to | ||
| // read by pairing sets of coordinates per line. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.