From 1a33ed7221733b181726c7ad01e4ce190bcf1d05 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 17:25:24 -0300 Subject: One Look path: Enter, Tab and a bare Look take what a click at the cursor takes, a diff line's address included, and an event reader is told of them as of a click The keyboard expanded the cursor's word on its own (look.expandedSel), so a diff line's address (and a hunk line's code word, without its +/-) was the mouse's alone: Enter on `+line` looked for the text in the diff. Now exec.operandAt is the one expansion: pointerOperand is it with a gesture's selection borrowed, the Enter/Tab path and a bare Look (exec.currentOperand) are it from the cursor, and both dispatch through mouse.dispatchPointerBuiltin, so a pane with an event reader hears a key's look or exec as a click's. A test runs one diff line and one file:12:5: line through a B3 click, a B3 sweep, Enter, a bare Look, the look file and an event write-back, and asserts each lands in the same file, line and column. Co-Authored-By: Claude Opus 5.5 --- src/exec.zig | 98 +++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 58 insertions(+), 40 deletions(-) (limited to 'src/exec.zig') diff --git a/src/exec.zig b/src/exec.zig index f3819a83..8ddb9abd 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -108,6 +108,23 @@ pub const PointerOperand = struct { }; pub fn pointerOperand(p: *Pardes, pane: *Pane, clicked: Pane.Sel) PointerOperand { + return operandAt(p, pane, clicked, true); +} + +/// The body's explicit selection, else what a look at its cursor takes. +pub fn currentOperand(p: *Pardes, pane: *Pane) ?[]const u8 { + if (pane.file == null and !pane.isTerminal()) return null; + if ((pane.body.vsel.active and pane.body.vsel.explicit) or pane.body.msel.active) return edit.currentSelText(p, pane); + return operandAt(p, pane, look.cursorWordSel(p, pane), false).text; +} + +/// What a look or an exec at `clicked` takes: the ONE expansion every +/// entry point uses (a B2/B3 click, Enter and Tab in normal mode, a bare +/// Look), so a diff line, a hunk line's code word and a file word expand +/// alike however they are reached (look.zig's "every entry point" test). +/// `borrow`: a gesture inside a kept or modal selection takes it; the +/// keyboard has said which selection it means already. +pub fn operandAt(p: *Pardes, pane: *Pane, clicked: Pane.Sel, borrow: bool) PointerOperand { const visible = clicked.r0 - @as(i32, pane.tag_rows); const wrapped = pane.wrapAt(visible); const raw = pane.isTerminal() and pane.body.mode == .tty and clicked.r0 >= pane.tag_rows; @@ -126,48 +143,49 @@ pub fn pointerOperand(p: *Pardes, pane: *Pane, clicked: Pane.Sel) PointerOperand else clicked.c0; var result: PointerOperand = .{ .row = row, .col = col }; - - const kept = pane.sel[sel_slot]; - const inside_kept = if (pane.pointerSelection(kept)) |rows| inside: { - if (clicked.r0 < pane.tag_rows) break :inside false; - if (pane.file != null and clicked.c0 < panes.File.gutterWidth(pane)) break :inside false; - const source_col: usize = @intCast(@max(0, col)); - for (rows) |span| { - const target = if (span.raw_terminal) - (if (raw) span.row else pane.surfRow(span.row)) - else - (if (raw) pane.gridRow(span.row) else span.row); - var lo = span.lo; - var hi = span.hi; - if (span.raw_terminal and !raw) { - lo -|= span.prompt_bytes; - hi -|= span.prompt_bytes; - } else if (!span.raw_terminal and raw) { - const prefix = panes.terminal.promptPrefixBytes(pane, row, raw_line); - lo += prefix; - hi += prefix; + if (borrow) { + const kept = pane.sel[sel_slot]; + const inside_kept = if (pane.pointerSelection(kept)) |rows| inside: { + if (clicked.r0 < pane.tag_rows) break :inside false; + if (pane.file != null and clicked.c0 < panes.File.gutterWidth(pane)) break :inside false; + const source_col: usize = @intCast(@max(0, col)); + for (rows) |span| { + const target = if (span.raw_terminal) + (if (raw) span.row else pane.surfRow(span.row)) + else + (if (raw) pane.gridRow(span.row) else span.row); + var lo = span.lo; + var hi = span.hi; + if (span.raw_terminal and !raw) { + lo -|= span.prompt_bytes; + hi -|= span.prompt_bytes; + } else if (!span.raw_terminal and raw) { + const prefix = panes.terminal.promptPrefixBytes(pane, row, raw_line); + lo += prefix; + hi += prefix; + } + if (target == row and source_col >= lo and source_col < hi) + break :inside true; } - if (target == row and source_col >= lo and source_col < hi) - break :inside true; + break :inside false; + } else edit.spanHas(clicked.r0, clicked.c0, kept.r0, kept.c0, kept.r1, kept.c1); + if (kept.state == .done and inside_kept) { + result.text = edit.selectionText(p, pane, kept) catch null; + result.preview = kept; + return result; + } + if (pane.body.vsel.active and pane.body.vsel.explicit and + edit.spanHas(row, col, pane.body.vsel.row, pane.body.vsel.col, pane.body.cur_row, pane.body.cur_col)) + { + result.text = edit.currentSelText(p, pane); + return result; + } + if (pane.body.msel.active and row >= @min(pane.body.msel.r0, pane.body.msel.r1) and + row <= @max(pane.body.msel.r0, pane.body.msel.r1)) + { + result.text = edit.currentSelText(p, pane); + return result; } - break :inside false; - } else edit.spanHas(clicked.r0, clicked.c0, kept.r0, kept.c0, kept.r1, kept.c1); - if (kept.state == .done and inside_kept) { - result.text = edit.selectionText(p, pane, kept) catch null; - result.preview = kept; - return result; - } - if (pane.body.vsel.active and pane.body.vsel.explicit and - edit.spanHas(row, col, pane.body.vsel.row, pane.body.vsel.col, pane.body.cur_row, pane.body.cur_col)) - { - result.text = edit.currentSelText(p, pane); - return result; - } - if (pane.body.msel.active and row >= @min(pane.body.msel.r0, pane.body.msel.r1) and - row <= @max(pane.body.msel.r0, pane.body.msel.r1)) - { - result.text = edit.currentSelText(p, pane); - return result; } // In a diff, a file or hunk header, and a hunk line pointed at on its // `+`/`-`/` ` prefix, expand to the whole line (the hover shows it) and -- cgit v1.3