From 0bc1ca8f9d3ece476ac469fc0a180793aab412df Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 01:29:25 -0300 Subject: A write to sel leaves the text it wrote selected, as acme's wrsel does, so cat sel reads it back; one open's writes run on from the last The selection collapsed to a point after the write, so sel read back nothing. What is written is now dot, and the next write of the same open goes at its end and grows it (acme's wrselrange), so a shell's several writes make one selection; another open's write replaces it. Co-Authored-By: Claude Opus 5.5 --- src/ninep/pane.zig | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) (limited to 'src/ninep/pane.zig') diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index 852a9ec0..9aada3ee 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -48,6 +48,11 @@ pub const State = struct { /// follows sets it or, refused, leaves it as it was; with none, the /// open's release empties it (tree.zig releaseHandle). dot_truncated: bool = false, + /// The open whose writes to `sel` are being kept together (0: none) and + /// the text they have made so far: the next write of that open goes at + /// its end, as acme's wrselrange does. + wrsel_by: u32 = 0, + wrsel: Range = .{}, /// Opens of `event`, which hold the pane scripted. readers: u16 = 0, /// One of `readers` reads `event`; a second reading open is refused. @@ -787,12 +792,20 @@ fn writeData(p: *Pardes, req: Req, pane: *Pane) Reply { fn writeSel(p: *Pardes, req: Req, pane: *Pane) Reply { if (fileOf(pane) == null) return tree.failText(req.tag, E.INVAL, e_no_text); - const d = dotOf(pane); + const pf = &pane.fs; + // The same open's next write follows its last; any other replaces dot. + const going_on = req.handle != 0 and pf.wrsel_by == req.handle; + const d = if (going_on) State.Range{ .q0 = pf.wrsel.q1, .q1 = pf.wrsel.q1 } else dotOf(pane); const q0: usize = d.q0; const q1: usize = @max(q0, @as(usize, d.q1)); const take = spliceBody(p, pane, q0, q1, req.data) orelse return Reply.fail(req.tag, E.NOMEM); - setDot(pane, .{ .q0 = clip(q0 + take), .q1 = clip(q0 + take) }); + // What was written is selected, as acme's wrsel leaves it: `cat sel` + // reads it back. + const start = if (going_on) pf.wrsel.q0 else clip(q0); + pf.wrsel = .{ .q0 = start, .q1 = clip(q0 + take) }; + pf.wrsel_by = req.handle; + setDot(pane, pf.wrsel); return .{ .tag = req.tag, .written = @intCast(take) }; } @@ -1265,9 +1278,17 @@ test "sel reads the selection and writing sel replaces it" { try testing.expectEqual(Status.ok, wr(p, sel, "ONE").reply.status); try testing.expectEqualStrings("ONE\ntwo\n", p.panes[0].?.file.?.content); + // What was written is selected, as acme's wrsel leaves it. + try testing.expectEqualStrings("ONE", rd(p, sel, 0, 100).bytes); + // A write of another open replaces it; one open's writes run on. _ = wr(p, sel, "!"); - try testing.expectEqualStrings("ONE!\ntwo\n", p.panes[0].?.file.?.content); - try testing.expectEqual(@as(u64, 0), call(p, .{ .tag = 2, .op = .getattr, .node = sel }).reply.attr.size); + try testing.expectEqualStrings("!\ntwo\n", p.panes[0].?.file.?.content); + const hs = call(p, .{ .tag = 3, .op = .open, .node = sel, .omode = 1 }).reply.handle; + _ = call(p, .{ .tag = 4, .op = .write, .node = sel, .handle = hs, .data = "a" }); + _ = call(p, .{ .tag = 5, .op = .write, .node = sel, .handle = hs, .data = "b" }); + _ = call(p, .{ .tag = 6, .op = .release, .node = sel, .handle = hs, .opened = true }); + try testing.expectEqualStrings("ab\ntwo\n", p.panes[0].?.file.?.content); + try testing.expectEqualStrings("ab", rd(p, sel, 0, 100).bytes); } test "the flag files read and write the state acme kept behind ctl verbs" { -- cgit v1.3