mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-09-05 19:08:17 +00:00
Mouse drag while clicked should cancel any mouse link actions (#7080)
Fixes #7077 This follows pretty standard behavior across native or popular applications on both platforms macOS and Linux. The basic behavior is that if you do a mouse down event and then drag the mouse beyond the current character, then any mouse up actions are canceled (beyond emiting the event itself). This fixes a specific scenario where you could do the following: 1. Click anywhere (mouse down) 2. Drag over a valid link 3. Press command/control (to activate the link) 4. Release the mouse button (mouse up) 5. The link is triggered Now, step 3 and step 5 do not happen. Links are not even highlighted in this scenario. This matches iTerm2 on macOS which has a similar command-to-activate-links behavior. ## Demo https://github.com/user-attachments/assets/f79767b1-78fd-432b-af46-28194b816747
This commit is contained in:
100
src/Surface.zig
100
src/Surface.zig
@@ -1031,9 +1031,64 @@ fn mouseRefreshLinks(
|
||||
// If the position is outside our viewport, do nothing
|
||||
if (pos.x < 0 or pos.y < 0) return;
|
||||
|
||||
// Update the last point that we checked for links so we don't
|
||||
// recheck if the mouse moves some pixels to the same point.
|
||||
self.mouse.link_point = pos_vp;
|
||||
|
||||
if (try self.linkAtPos(pos)) |link| {
|
||||
// We use an arena for everything below to make things easy to clean up.
|
||||
// In the case we don't do any allocs this is very cheap to setup
|
||||
// (effectively just struct init).
|
||||
var arena = ArenaAllocator.init(self.alloc);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
// Get our link at the current position. This returns null if there
|
||||
// isn't a link OR if we shouldn't be showing links for some reason
|
||||
// (see further comments for cases).
|
||||
const link_: ?apprt.action.MouseOverLink = link: {
|
||||
// If we clicked and our mouse moved cells then we never
|
||||
// highlight links until the mouse is unclicked. This follows
|
||||
// standard macOS and Linux behavior where a click and drag cancels
|
||||
// mouse actions.
|
||||
const left_idx = @intFromEnum(input.MouseButton.left);
|
||||
if (self.mouse.click_state[left_idx] == .press) click: {
|
||||
const pin = self.mouse.left_click_pin orelse break :click;
|
||||
const click_pt = self.io.terminal.screen.pages.pointFromPin(
|
||||
.viewport,
|
||||
pin.*,
|
||||
) orelse break :click;
|
||||
|
||||
if (!click_pt.coord().eql(pos_vp)) {
|
||||
log.debug("mouse moved while left click held, ignoring link hover", .{});
|
||||
break :link null;
|
||||
}
|
||||
}
|
||||
|
||||
const link = (try self.linkAtPos(pos)) orelse break :link null;
|
||||
switch (link[0]) {
|
||||
.open => {
|
||||
const str = try self.io.terminal.screen.selectionString(alloc, .{
|
||||
.sel = link[1],
|
||||
.trim = false,
|
||||
});
|
||||
break :link .{ .url = str };
|
||||
},
|
||||
|
||||
._open_osc8 => {
|
||||
// Show the URL in the status bar
|
||||
const pin = link[1].start();
|
||||
const uri = self.osc8URI(pin) orelse {
|
||||
log.warn("failed to get URI for OSC8 hyperlink", .{});
|
||||
break :link null;
|
||||
};
|
||||
break :link .{ .url = uri };
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// If we found a link, setup our internal state and notify the
|
||||
// apprt so it can highlight it.
|
||||
if (link_) |link| {
|
||||
self.renderer_state.mouse.point = pos_vp;
|
||||
self.mouse.over_link = true;
|
||||
self.renderer_state.terminal.screen.dirty.hyperlink_hover = true;
|
||||
@@ -1042,38 +1097,18 @@ fn mouseRefreshLinks(
|
||||
.mouse_shape,
|
||||
.pointer,
|
||||
);
|
||||
|
||||
switch (link[0]) {
|
||||
.open => {
|
||||
const str = try self.io.terminal.screen.selectionString(self.alloc, .{
|
||||
.sel = link[1],
|
||||
.trim = false,
|
||||
});
|
||||
defer self.alloc.free(str);
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_over_link,
|
||||
.{ .url = str },
|
||||
);
|
||||
},
|
||||
|
||||
._open_osc8 => link: {
|
||||
// Show the URL in the status bar
|
||||
const pin = link[1].start();
|
||||
const uri = self.osc8URI(pin) orelse {
|
||||
log.warn("failed to get URI for OSC8 hyperlink", .{});
|
||||
break :link;
|
||||
};
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_over_link,
|
||||
.{ .url = uri },
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_over_link,
|
||||
link,
|
||||
);
|
||||
try self.queueRender();
|
||||
} else if (over_link) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No link, if we're previously over a link then we need to clear
|
||||
// the over-link apprt state.
|
||||
if (over_link) {
|
||||
_ = try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.mouse_shape,
|
||||
@@ -1085,6 +1120,7 @@ fn mouseRefreshLinks(
|
||||
.{ .url = "" },
|
||||
);
|
||||
try self.queueRender();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user