summaryrefslogtreecommitdiff
path: root/src/ninep/events.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 22:58:16 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commitc27222be8ba77a3a6bdda16978f3001510bde055 (patch)
tree46e709eba262af80b516ade483d210400a09455f /src/ninep/events.zig
parent4f65855f372186406f4e48708ec3d5eb9aceb6d3 (diff)
downloadpardes-c27222be8ba77a3a6bdda16978f3001510bde055.tar.gz
pardes-c27222be8ba77a3a6bdda16978f3001510bde055.zip
One diff-aware expansion for a click, a 9P whole-line look and an empty event write-back; a blank context line resolves to its path:line
The mouse expanded a diff line by its kind (a header or a hunk line's prefix to the line's address, a hunk line's code to words), but an empty look written back to event used the plain word expansion, so ML on a `+` took nothing, and a 9P look of a blank context line (` `) was refused as whitespace. look.diffClick is now the one answer: the mouse, the whole-line look and the event write-back all ask it, and a blank context line is a line like any other. A test asks all three on each kind of line. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/events.zig')
-rw-r--r--src/ninep/events.zig24
1 files changed, 20 insertions, 4 deletions
diff --git a/src/ninep/events.zig b/src/ninep/events.zig
index 9752e287..4d1bbe64 100644
--- a/src/ninep/events.zig
+++ b/src/ninep/events.zig
@@ -685,11 +685,27 @@ fn scanNumber(data: []const u8, i: *usize) ?u32 {
/// Writing a Look or Exec record back performs the action it names.
/// The word a click at `at` expands to: the file name or word run under it
/// on its line (look.expandedWord), else nothing.
-fn expandAt(whole: []const u8, at: usize) []const u8 {
+/// An empty range written back: what a click at `at` takes, as the mouse's
+/// does (exec.pointerOperand). In a diff pane's body that is look.diffClick's
+/// answer, the line's address or its code's words.
+pub fn expandAt(p: *Pardes, pane: *Pane, whole: []const u8, at: usize, in_body: bool) []const u8 {
const start = if (std.mem.lastIndexOfScalar(u8, whole[0..at], '\n')) |nl| nl + 1 else 0;
const end = std.mem.indexOfScalarPos(u8, whole, at, '\n') orelse whole.len;
- const b = look.expandedWord(whole[start..end], at - start) orelse return "";
- return whole[start + b.lo .. start + b.hi];
+ var word_from: usize = 0;
+ if (in_body) diff: {
+ const row = std.mem.count(u8, whole[0..start], "\n");
+ // Only when the served line is the one the diff reads there.
+ const lines = look.diffLines(p, pane) orelse break :diff;
+ if (row >= lines.len or !std.mem.eql(u8, std.mem.trimEnd(u8, lines[row], "\r"), std.mem.trimEnd(u8, whole[start..end], "\r"))) break :diff;
+ var buf: [4096 + 32]u8 = undefined;
+ const click = look.diffClick(p, pane, row, at - start, &buf) orelse break :diff;
+ if (click.address) |address| return p.scratch.allocator().dupe(u8, address) catch "";
+ word_from = click.word_from;
+ }
+ const line = whole[start..end];
+ if (at - start < word_from or word_from > line.len) return "";
+ const b = look.expandedWord(line[word_from..], at - start - word_from) orelse return "";
+ return whole[start + word_from + b.lo .. start + word_from + b.hi];
}
pub fn writeEvent(p: *Pardes, asked: Req, id: usize) Reply {
@@ -730,7 +746,7 @@ pub fn writeEvent(p: *Pardes, asked: Req, id: usize) Reply {
// An empty range with no text is a click at that point: expanded as
// the click would be, to the word or file name under it (acme's
// expand, look.c).
- const said = if (r.q0 == r.q1) r.text orelse expandAt(whole, lo) else whole[lo..hi];
+ const said = if (r.q0 == r.q1) r.text orelse expandAt(p, pane, whole, lo, !r.action.onTag()) else whole[lo..hi];
const text = p.scratch.allocator().dupe(u8, said) catch continue;
switch (r.action) {
// A body's click written back is done as the click would have