summaryrefslogtreecommitdiff
path: root/src/ninep
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 17:08:18 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit4de0be4942b5aad143949826523ce68a0b1b03cc (patch)
tree93f05caed309439c426155852a39873c2c521a27 /src/ninep
parent0b7bec65958078ca066aa124e88e96d9cca6e926 (diff)
downloadpardes-4de0be4942b5aad143949826523ce68a0b1b03cc.tar.gz
pardes-4de0be4942b5aad143949826523ce68a0b1b03cc.zip
An empty range written back to event acts on the word a click there expands to, as acme's expand does
`ML960 960` or `MX1 1` written back with no text acted on the empty range between the offsets: a look of nothing, an exec of nothing. A click at a point expands to the word or file name under it, and acme's written-back event does the same. It now expands with look.expandedWord, as a no-drag click does, on its line of the body or tag. fs.md says so. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep')
-rw-r--r--src/ninep/events.zig30
1 files changed, 29 insertions, 1 deletions
diff --git a/src/ninep/events.zig b/src/ninep/events.zig
index 63405781..fa90fe71 100644
--- a/src/ninep/events.zig
+++ b/src/ninep/events.zig
@@ -688,6 +688,15 @@ 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 {
+ 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];
+}
+
pub fn writeEvent(p: *Pardes, asked: Req, id: usize) Reply {
const pane0 = p.panes[id] orelse return Reply.fail(asked.tag, E.NOENT);
// Its last record needs no newline, as every other line file's last
@@ -723,7 +732,10 @@ pub fn writeEvent(p: *Pardes, asked: Req, id: usize) Reply {
const whole = if (r.action.onTag()) pane_files.tagOf(p, pane) else pane_files.bodyOf(pane);
const lo = @min(@as(usize, r.q0), whole.len);
const hi = @max(lo, @min(@as(usize, r.q1), whole.len));
- const said = if (r.q0 == r.q1) r.text orelse whole[lo..hi] else whole[lo..hi];
+ // 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 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
@@ -1237,6 +1249,22 @@ test "a pane named before it is announced has its new under that name, and no re
try testing.expect(std.mem.indexOf(u8, after.bytes, try std.fmt.bufPrint(&want, "rename {d} ", .{shell.serial})) == null);
}
+test "an empty range written back is expanded as a click at that point would be" {
+ const p = try withFile(testing.allocator, "abc def abc\nx Newcol y\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ const pane = p.panes[p.paneBySerial(serial).?].?;
+ const event = Node.of(serial, .event);
+ // A look at 1 is a look at `abc`: from dot, past the first, the next.
+ pane_files.setDot(pane, .{ .q0 = 4, .q1 = 4 });
+ try testing.expectEqual(Status.ok, wr(p, event, "ML1 1\n").reply.status);
+ try testing.expectEqual(pane_files.State.Range{ .q0 = 8, .q1 = 11 }, pane_files.dotOf(pane));
+ // An exec at 15 runs the word there, `Newcol`.
+ const cols = p.ncol;
+ try testing.expectEqual(Status.ok, wr(p, event, "MX15 15\n").reply.status);
+ try testing.expectEqual(cols + 1, p.ncol);
+}
+
test "opens of the log share the open records, and a closed one frees its record" {
const gpa = testing.allocator;
const p = try withFile(gpa, "x\n");