summaryrefslogtreecommitdiff
path: root/src/mouse.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 19:20:20 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commitb08322345b33abcee114c05c85fa4c46c10f4701 (patch)
tree645cbacce7ef1d5c7abd71f0b76a3c72e763835d /src/mouse.zig
parent8429e7bd8aff1ae0c71d3b1a7a6b9f01ed52e691 (diff)
downloadpardes-b08322345b33abcee114c05c85fa4c46c10f4701.tar.gz
pardes-b08322345b33abcee114c05c85fa4c46c10f4701.zip
A double-click selects as acme's does: a word, a line, a bracketed or quoted text
A second B1 click at the same cell selected nothing more. Now, as acme's textdoubleclick (plan9port text.c:1407), it selects up to the match of a bracket just before or after the point, the whole line at a line's start or end, the quoted text inside ' " `, or else the word, in bodies, tags and terminals in normal mode. A plain click still puts the caret on its cell. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/mouse.zig')
-rw-r--r--src/mouse.zig32
1 files changed, 32 insertions, 0 deletions
diff --git a/src/mouse.zig b/src/mouse.zig
index 2dcd80ed..ac3e206b 100644
--- a/src/mouse.zig
+++ b/src/mouse.zig
@@ -755,6 +755,36 @@ fn mirrorTtySelection(p: *Pardes, pane: *Pane) void {
edit.setYank(p, text);
}
+/// acme's double-click (text.c:881 textselect, plan9port text.c:1407
+/// textdoubleclick): a second plain B1 click at the same cell within half a
+/// second selects what edit.doubleClickRange says, from the caret the click
+/// left. A plain click still puts the caret on its cell.
+fn doubleClick(p: *Pardes, id: usize, t: *panes.Text, on_tag: bool, row: i32, col: i32) void {
+ const now = clockMs();
+ const prev = p.last_click;
+ p.last_click = .{ .id = id, .on_tag = on_tag, .row = row, .col = col, .ms = now };
+ const c = prev orelse return;
+ if (c.id != id or c.on_tag != on_tag or c.row != row or c.col != col or now - c.ms > 500) return;
+ p.last_click = null;
+ const text = edit.flatSurface(p, t) catch return;
+ const q = modal.offsetAt(text, .{ .row = @intCast(@max(0, t.cur_row)), .col = @intCast(@max(0, t.cur_col)) });
+ const r = edit.doubleClickRange(text, q);
+ if (r[1] <= r[0]) return;
+ const a = modal.positionAt(text, r[0]);
+ const h = modal.positionAt(text, modal.prevGrapheme(text, r[1]));
+ t.vsel = .{ .active = true, .row = @intCast(a.row), .col = @intCast(a.col), .explicit = true };
+ t.cur_row = @intCast(h.row);
+ t.cur_col = @intCast(h.col);
+ t.cur_pinned = true;
+}
+
+fn clockMs() i64 {
+ if (comptime !pardes.hosted) return 0;
+ var ts: std.c.timespec = undefined;
+ if (std.c.clock_gettime(.MONOTONIC, &ts) != 0) return 0;
+ return @as(i64, ts.sec) * 1000 + @divTrunc(@as(i64, ts.nsec), 1_000_000);
+}
+
fn dragRelease(p: *Pardes, button: Mouse.Button) void {
// A different button's release belongs to a chord or another host
// stream; only the button which began this gesture may end it.
@@ -838,6 +868,7 @@ fn dragRelease(p: *Pardes, button: Mouse.Button) void {
} else if (tagline.tagOffsetAt(p, pane, row, col)) |cursor| {
pane.tag.cur_row = @intCast(cursor.row);
pane.tag.cur_col = @intCast(cursor.col);
+ if (!dragged) doubleClick(p, s.id, &pane.tag, true, sl.r1, sl.c1);
}
return;
}
@@ -863,6 +894,7 @@ fn dragRelease(p: *Pardes, button: Mouse.Button) void {
pane.body.select = false;
pane.body.restore_cursor = false;
pane.body.sticky_col = -1;
+ if (!dragged) doubleClick(p, s.id, &pane.body, false, sl.r1, sl.c1);
if (s.ctrl) p.lspRequest(s.id, .definition, "");
}
} else {