summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 01:29:25 -0300
committerGabriel Schneider <[email protected]>2026-10-01 01:35:12 -0300
commit0bc1ca8f9d3ece476ac469fc0a180793aab412df (patch)
treeb01e02c7ff96be098a2b1543e6069836ef669fa5 /src
parentb3bbad85c61f81c01fb983813ccc28ceb6361469 (diff)
downloadpardes-0bc1ca8f9d3ece476ac469fc0a180793aab412df.tar.gz
pardes-0bc1ca8f9d3ece476ac469fc0a180793aab412df.zip
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 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/ninep/pane.zig29
-rw-r--r--src/ninep/tree.zig8
2 files changed, 32 insertions, 5 deletions
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" {
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index c71a706f..cc8f938a 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -843,7 +843,8 @@ fn open(p: *Pardes, req: Req, target: Target) Reply {
const reader = req.omode & 3 != 1; // OWRITE is the one mode that never reads
break :what switch (t.file) {
.body => if (pn.isTerminal()) .{ .snapshot = null } else if (req.omode & 3 == 0) return .{ .tag = req.tag } else .writes,
- .data, .xdata => if (req.omode & 3 != 0) .writes else return .{ .tag = req.tag },
+ // sel too: one open's writes run on from its last (wrsel).
+ .data, .xdata, .sel => if (req.omode & 3 != 0) .writes else return .{ .tag = req.tag },
// Reads of these consume, so two readers would each see half
// the stream; the second is refused rather than robbed.
.event => if (reader and pn.fs.event_reader) return failText(req.tag, E.BUSY, e_in_use) else .{ .event = reader },
@@ -932,6 +933,11 @@ fn releaseHandle(p: *Pardes, req: Req) void {
};
// The writes of one open are one undo step (pane.zig `joined`): its
// close ends that step.
+ // A sel open's run of writes ends with it.
+ if (t == .pane and t.pane.file == .sel) if (p.paneBySerial(t.pane.serial)) |id| {
+ const pf = &p.panes[id].?.fs;
+ if (pf.wrsel_by == req.handle) pf.wrsel_by = 0;
+ };
if (t == .pane and (t.pane.file == .body or t.pane.file == .data or t.pane.file == .xdata)) if (p.paneBySerial(t.pane.serial)) |id| {
const pf = &p.panes[id].?.fs;
if (pf.joined_by == 0 or pf.joined_by == req.handle) pf.joined = null;