mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
core: handle mouse capture events with link highlighting
Fixes #1416 At a high level, the issue is that when mouse capture is enabled (i.e. in neovim), "shift" escapes the capture. So "cmd+shift" is equal to "cmd" which doesn't get sent to the TUI program and so on. For link highlighting which now requires "cmd" (super) is held, we were sending "cmd+shift" to the renderer so we weren't checking for links. So the core of this commit is respecting this scenario and stripping the shift modifier. This commit also found that when the mouse wasn't over a link, we were always checking and highlighting links on line one of the visible screen. This bug is fixed and should also result in a very slight performance improvement on rendering in all cases.
This commit is contained in:
@ -854,7 +854,7 @@ fn modsChanged(self: *Surface, mods: input.Mods) void {
|
|||||||
{
|
{
|
||||||
self.renderer_state.mutex.lock();
|
self.renderer_state.mutex.lock();
|
||||||
defer self.renderer_state.mutex.unlock();
|
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| {
|
self.queueRender() catch |err| {
|
||||||
@ -2372,6 +2372,9 @@ fn linkAtPos(
|
|||||||
break :mouse_pt viewport_point.toScreen(&self.io.terminal.screen);
|
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.
|
// Get the line we're hovering over.
|
||||||
const line = self.io.terminal.screen.getLine(mouse_pt) orelse
|
const line = self.io.terminal.screen.getLine(mouse_pt) orelse
|
||||||
return null;
|
return null;
|
||||||
@ -2382,7 +2385,7 @@ fn linkAtPos(
|
|||||||
for (self.config.links) |link| {
|
for (self.config.links) |link| {
|
||||||
switch (link.highlight) {
|
switch (link.highlight) {
|
||||||
.always, .hover => {},
|
.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);
|
var it = strmap.searchIterator(link.regex);
|
||||||
@ -2398,6 +2401,25 @@ fn linkAtPos(
|
|||||||
return null;
|
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
|
/// Attempt to invoke the action of any link that is under the
|
||||||
/// given position.
|
/// given position.
|
||||||
///
|
///
|
||||||
|
@ -36,6 +36,11 @@ pub const Highlight = union(enum) {
|
|||||||
/// hovering or always. For always, all links will be highlighted
|
/// hovering or always. For always, all links will be highlighted
|
||||||
/// when the mods are pressed regardless of if the mouse is hovering
|
/// when the mods are pressed regardless of if the mouse is hovering
|
||||||
/// over them.
|
/// 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,
|
always_mods: Mods,
|
||||||
hover_mods: Mods,
|
hover_mods: Mods,
|
||||||
};
|
};
|
||||||
|
@ -1551,12 +1551,12 @@ fn rebuildCells(
|
|||||||
const arena_alloc = arena.allocator();
|
const arena_alloc = arena.allocator();
|
||||||
|
|
||||||
// Create our match set for the links.
|
// 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,
|
arena_alloc,
|
||||||
screen,
|
screen,
|
||||||
mouse.point orelse .{},
|
mouse_pt,
|
||||||
mouse.mods,
|
mouse.mods,
|
||||||
);
|
) else .{};
|
||||||
|
|
||||||
// Determine our x/y range for preedit. We don't want to render anything
|
// Determine our x/y range for preedit. We don't want to render anything
|
||||||
// here because we will render the preedit separately.
|
// here because we will render the preedit separately.
|
||||||
|
@ -987,12 +987,12 @@ pub fn rebuildCells(
|
|||||||
self.gl_cells_written = 0;
|
self.gl_cells_written = 0;
|
||||||
|
|
||||||
// Create our match set for the links.
|
// 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,
|
arena_alloc,
|
||||||
screen,
|
screen,
|
||||||
mouse.point orelse .{},
|
mouse_pt,
|
||||||
mouse.mods,
|
mouse.mods,
|
||||||
);
|
) else .{};
|
||||||
|
|
||||||
// Determine our x/y range for preedit. We don't want to render anything
|
// Determine our x/y range for preedit. We don't want to render anything
|
||||||
// here because we will render the preedit separately.
|
// here because we will render the preedit separately.
|
||||||
|
@ -139,7 +139,7 @@ pub const MatchSet = struct {
|
|||||||
/// The matches.
|
/// The matches.
|
||||||
///
|
///
|
||||||
/// Important: this must be in left-to-right top-to-bottom order.
|
/// Important: this must be in left-to-right top-to-bottom order.
|
||||||
matches: []const terminal.Selection,
|
matches: []const terminal.Selection = &.{},
|
||||||
i: usize = 0,
|
i: usize = 0,
|
||||||
|
|
||||||
pub fn deinit(self: *MatchSet, alloc: Allocator) void {
|
pub fn deinit(self: *MatchSet, alloc: Allocator) void {
|
||||||
|
Reference in New Issue
Block a user