summaryrefslogtreecommitdiff
path: root/src/ninep
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 02:15:43 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit9391d35d357ebf18759c10f6c756855e5a8f7b50 (patch)
tree181645499d1728c12806dabb1ee65cb61e9f5a41 /src/ninep
parent5492da0ba1d5f1c1cc0a880f82107961627c9ba6 (diff)
downloadpardes-9391d35d357ebf18759c10f6c756855e5a8f7b50.tar.gz
pardes-9391d35d357ebf18759c10f6c756855e5a8f7b50.zip
A limit write that fails keeps the limit it had
A shell's > truncates limit, which lifted it, before the write; a write that then failed left no limit at all. The truncation now keeps the old limit until that open's write, and a failed write puts it back; a bare truncation still lifts it once its open is released. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep')
-rw-r--r--src/ninep/pane.zig28
-rw-r--r--src/ninep/tree.zig4
2 files changed, 31 insertions, 1 deletions
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig
index 902f4f2a..e7b72c88 100644
--- a/src/ninep/pane.zig
+++ b/src/ninep/pane.zig
@@ -41,6 +41,9 @@ pub const State = struct {
/// rather than act at the address before it, which acme would do.
addr_failed: bool = false,
limit: ?Range = null,
+ /// The limit a truncation cleared, kept until the write that follows it
+ /// on that open (a shell's `>`): a write that fails puts it back.
+ limit_truncated: ?Range = null,
/// Opens of `event`, which hold the pane scripted.
readers: u16 = 0,
/// One of `readers` reads `event`; a second reading open is refused.
@@ -523,8 +526,13 @@ fn writeRange(req: Req, pane: *Pane, file: PaneFile) Reply {
};
const range = r orelse {
if (file == .addr) pf.addr_failed = true;
+ if (file == .limit) if (pf.limit_truncated) |kept| {
+ pf.limit = kept;
+ pf.limit_truncated = null;
+ };
return tree.failText(req.tag, E.INVAL, a.err);
};
+ if (file == .limit) pf.limit_truncated = null;
switch (file) {
.addr => {
pf.addr = range;
@@ -684,7 +692,10 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status {
// is evaluated from where the last one left off, as with `>>`.
// `0` (or `,`) is how to start over. See State.addr.
.addr => {},
- .limit => pf.limit = null,
+ .limit => {
+ pf.limit_truncated = pf.limit;
+ pf.limit = null;
+ },
.dot => if (fileOf(pane) != null) setDot(pane, .{}),
else => {},
}
@@ -1236,3 +1247,18 @@ test "addresses are in runes: a combining mark and a lone \\r are places of thei
_ = wr(p, addr_node, "#4,#5");
try testing.expectEqualStrings("\r", rd(p, Node.of(serial, .xdata), 0, 100).bytes);
}
+
+test "a limit write that fails keeps the limit a truncation cleared before it" {
+ const gpa = testing.allocator;
+ const p = try withFile(gpa, "one two\n");
+ defer p.deinit();
+ const limit = Node.of(serialOf(p), .limit);
+ _ = wr(p, limit, "#0,#3");
+ _ = call(p, .{ .tag = 1, .op = .setattr, .node = limit, .truncate = true });
+ try testing.expectEqual(Status.err, wr(p, limit, "/nomatch/").reply.status);
+ try testing.expectEqualStrings(" 0 3 ", rd(p, limit, 0, 100).bytes);
+ // A truncation with no write after it clears it.
+ _ = call(p, .{ .tag = 2, .op = .setattr, .node = limit, .truncate = true });
+ _ = call(p, .{ .tag = 3, .op = .release, .node = limit });
+ try testing.expectEqualStrings("", rd(p, limit, 0, 100).bytes);
+}
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index da9c37f0..278732b5 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -806,6 +806,10 @@ fn releaseHandle(p: *Pardes, req: Req) void {
p.panes[id].?.fs.tag_rewrite = false;
p.panes[id].?.fs.tag_held_newline = false;
};
+ // `: > limit` clears it for good once its open goes.
+ if (t == .pane and t.pane.file == .limit) if (p.paneBySerial(t.pane.serial)) |id| {
+ p.panes[id].?.fs.limit_truncated = null;
+ };
if (t == .col and t.col.file == .tag) cols.released(p, t.col.serial);
if (t == .top and t.top == .tag) cols.released(p, null);
const o = openOf(p, req) orelse return;