Skip to content

Commit

Permalink
surface: handle hyperlinks more reliably
Browse files Browse the repository at this point in the history
We refresh the link hover state in two (generic) cases

1. When the modifiers change
2. When the cursor changes position

Each of these have additional state qualifiers. Modify the qualifiers
such that we refresh links under the following scenarios:

1. Modifiers change
  - Control is pressed (this is handled in the renderer)
  - Mouse reporting is off
    OR
    Mouse reporting is on AND shift is pressed AND we are NOT reporting
    shift to the terminal

2. Cursor changes position
  - Control is pressed (this is handled in the renderer)
  - We previously were over a link
  - The position changed (or we had no previous position)
  - Mouse reporting is off
    OR
    Mouse reporting is on AND shift is pressed AND we are NOT reporting
    shift to the terminal

This fixes a few issues with the previous implementation:

1. If mouse reporting was on and you were over a link, pressing ctrl
   would enable link hover state. If you moved your mouse, you would
   exit that state. The logic in the keyCallback and the
   cursorPosCallback was not the same. Now, they both check for the same
   set of conditions
2. If mouse reporting was off, you could hold control and move the mouse
   to discover links. If mouse reporting was on, holding control + shift
   would not allow you to discover links. You had to be hovering one
   when you pressed the modifiers. Previously, we only refreshed links
   if we *weren't* reporting the mouse event. Now, we refresh links even
   even if we report a mouse event (ie a mouse motion event with the
   shift modifier pressed *will* hover links and also report events)
  • Loading branch information
rockorager committed Dec 29, 2024
1 parent 64c3937 commit e24f33a
Showing 1 changed file with 52 additions and 46 deletions.
98 changes: 52 additions & 46 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1703,16 +1703,37 @@ pub fn keyCallback(
// Update our modifiers, this will update mouse mods too
self.modsChanged(event.mods);

// Refresh our link state
const pos = self.rt_surface.getCursorPos() catch break :mouse_mods;
self.mouseRefreshLinks(
pos,
self.posToViewport(pos.x, pos.y),
self.mouse.over_link,
) catch |err| {
log.warn("failed to refresh links err={}", .{err});
break :mouse_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)))
{
// Refresh our link state
const pos = self.rt_surface.getCursorPos() catch break :mouse_mods;
self.mouseRefreshLinks(
pos,
self.posToViewport(pos.x, pos.y),
self.mouse.over_link,
) catch |err| {
log.warn("failed to refresh links err={}", .{err});
break :mouse_mods;
};
} else if (self.io.terminal.flags.mouse_event != .none and !self.mouse.mods.shift) {
// If we have mouse reports on and we don't have shift pressed, we reset state
try self.rt_app.performAction(
.{ .surface = self },
.mouse_shape,
self.io.terminal.mouse_shape,
);
try self.rt_app.performAction(
.{ .surface = self },
.mouse_over_link,
.{ .url = "" },
);
try self.queueRender();
}
}

// Process the cursor state logic. This will update the cursor shape if
Expand Down Expand Up @@ -3343,6 +3364,27 @@ pub fn cursorPosCallback(
try self.queueRender();
}

// Handle link hovering
// We refresh links when
// 1. we were previously over a link
// OR
// 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
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))))
{
// 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
try self.mouseRefreshLinks(pos, pos_vp, over_link);
}

// Do a mouse report
if (self.io.terminal.flags.mouse_event != .none) report: {
// Shift overrides mouse "grabbing" in the window, taken from Kitty.
Expand All @@ -3363,18 +3405,6 @@ pub fn cursorPosCallback(

try self.mouseReport(button, .motion, self.mouse.mods, pos);

// If we were previously over a link, we need to undo the link state.
// We also queue a render so the renderer can undo the rendered link
// state.
if (over_link) {
try self.rt_app.performAction(
.{ .surface = self },
.mouse_over_link,
.{ .url = "" },
);
try self.queueRender();
}

// If we're doing mouse motion tracking, we do not support text
// selection.
return;
Expand Down Expand Up @@ -3430,30 +3460,6 @@ pub fn cursorPosCallback(

return;
}

// Handle link hovering
if (self.mouse.link_point) |last_vp| {
// Mark the link's row as dirty.
if (over_link) {
self.renderer_state.terminal.screen.dirty.hyperlink_hover = true;
}

// If our last link viewport point is unchanged, then don't process
// links. This avoids constantly reprocessing regular expressions
// for every pixel change.
if (last_vp.eql(pos_vp)) {
// We have to restore old values that are always cleared
if (over_link) {
self.mouse.over_link = over_link;
self.renderer_state.mouse.point = pos_vp;
}

return;
}
}

// We can process new links.
try self.mouseRefreshLinks(pos, pos_vp, over_link);
}

/// Double-click dragging moves the selection one "word" at a time.
Expand Down

0 comments on commit e24f33a

Please sign in to comment.