mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-28 07:45:20 +00:00
Extract click/drag selection handling into SelectionGesture (#12830)
Refactor terminal text selection into a reusable `SelectionGesture`
state machine. Most importantly, this means our click+drag logic around
selection is now fully unit tested! And we found bugs! And fixed them!
The large line increase in this diff is mainly comments + tests.
I've wanted to do this forever so we can unit test this, but I was
kicked in the butt to do it recently because reimplementing selection
logic in libghostty consumers turns out to be complex and error prone
and we have a perfectly battle tested logic machine here so why not
extract it?
Behavioral changes from main surfaced via unit testing:
- Dragging now drags by output across semantic output blocks when the
initial press was an output selection. This matches the behavior of
dragging continuing whatever the initial selection logic was.
- Selection autoscroll now stops when the click anchor is invalidated by
a screen change (e.g. primary to alt)
- Deep press (macOS force touch) now selects the word at the original
press location and consumes the active drag gesture, preventing later
movement from dragging or autoscrolling that selection. This matches
built-in macOS apps.
- Mouse release records whether the gesture moved away from the pressed
cell, so link and prompt clicks are skipped after a drag while normal
clicks still activate them.
Example usage:
```zig
var gesture: terminal.SelectionGesture = .init;
defer gesture.deinit(t);
const press_selection = try gesture.press(t, .{
.time = try std.time.Instant.now(),
.pin = press_pin,
.xpos = mouse_x,
.ypos = mouse_y,
.max_distance = cell_width,
.repeat_interval = mouse_interval,
.word_boundary_codepoints = selection_word_chars,
.behaviors = &.{ .cell, .word, .output },
});
try t.screens.active.select(press_selection);
if (gesture.drag(t, drag_event)) |drag_selection| {
try t.screens.active.select(drag_selection);
}
gesture.release(t, .{ .pin = release_pin });
```
This commit is contained in:
1049
src/Surface.zig
1049
src/Surface.zig
File diff suppressed because it is too large
Load Diff
@@ -462,7 +462,8 @@ fn mouseTable(
|
||||
|
||||
{
|
||||
const left_click_point: terminal.point.Coordinate = pt: {
|
||||
const p = surface_mouse.left_click_pin orelse break :pt .{};
|
||||
const p = surface_mouse.selection_gesture.validatedLeftClickPin(&t.screens) orelse
|
||||
break :pt .{};
|
||||
const pt = t.screens.active.pages.pointFromPin(
|
||||
.active,
|
||||
p.*,
|
||||
@@ -495,8 +496,8 @@ fn mouseTable(
|
||||
_ = cimgui.c.ImGui_TableSetColumnIndex(1);
|
||||
cimgui.c.ImGui_Text(
|
||||
"(%dpx, %dpx)",
|
||||
@as(u32, @intFromFloat(surface_mouse.left_click_xpos)),
|
||||
@as(u32, @intFromFloat(surface_mouse.left_click_ypos)),
|
||||
@as(u32, @intFromFloat(surface_mouse.selection_gesture.left_click_xpos)),
|
||||
@as(u32, @intFromFloat(surface_mouse.selection_gesture.left_click_ypos)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
2003
src/terminal/SelectionGesture.zig
Normal file
2003
src/terminal/SelectionGesture.zig
Normal file
File diff suppressed because it is too large
Load Diff
@@ -49,6 +49,7 @@ pub const Screen = @import("Screen.zig");
|
||||
pub const ScreenSet = @import("ScreenSet.zig");
|
||||
pub const Scrollbar = PageList.Scrollbar;
|
||||
pub const Selection = @import("Selection.zig");
|
||||
pub const SelectionGesture = @import("SelectionGesture.zig");
|
||||
pub const SizeReportStyle = csi.SizeReportStyle;
|
||||
pub const StringMap = @import("StringMap.zig");
|
||||
pub const Style = style.Style;
|
||||
|
||||
Reference in New Issue
Block a user