summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.agents/skills/pardes-9p/SKILL.md5
-rw-r--r--docs/fs.md8
-rw-r--r--src/ninep/pane.zig31
-rw-r--r--src/ninep/tree.zig4
4 files changed, 40 insertions, 8 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md
index 1fe2aed8..e5affebe 100644
--- a/.agents/skills/pardes-9p/SKILL.md
+++ b/.agents/skills/pardes-9p/SKILL.md
@@ -264,8 +264,9 @@ env printf 'Edit ,x/foo/{\ni/</\na/>/\n}\n' > $pane/ctl
| Delete that range | `: > $pane/data` | truncate `data` (open with OTRUNC) |
A write leaves `addr` just past what it wrote, so a second `echo x > data`
-inserts after the first: write `addr` again before each replacement. Each
-`data` write is one undo step; to make a loop's writes one, `echo 1 > mark`
+inserts after the first: write `addr` again before each replacement. The
+writes of one open of `data` are one undo step (a multi-line `printf` too);
+to make a loop's writes one, `echo 1 > mark`
(an undo point now), `echo 0 > mark`, the writes, then `echo 1 > mark`.
A `+New` scratch counts as unsaved (and holds up Exit, Restore, Del) only
once it holds 100 bytes or more. A read
diff --git a/docs/fs.md b/docs/fs.md
index f20b521a..1baa9b2c 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -728,9 +728,11 @@ missed its target cannot then write at the last one.
The three flag files `dirty`, `mark` and `scroll` read `0` or `1` and take
`0` or `1`: whether the buffer differs from its file, whether a write pushes
an undo point (writing `1` pushes one now), and whether a write scrolls the
-pane. Every `data`, `xdata` or `body` write is an undo step of its own
-while `mark` is 1; to make a loop's writes one step, write `1` (an undo
-point here), then `0`, the writes, then `1` again.
+pane. The writes of one open of `data`, `xdata` or `body` are one undo
+step while `mark` is 1 (so `printf 'a\nb\n' > data` is one, though a
+shell writes it a line at a time), and the next open starts another; to
+make a loop's writes one step, write `1` (an undo point here), then `0`,
+the writes, then `1` again.
One rule for what fails: a write fails whenever what it asked for fails,
whether it came to a `ctl` (the root's, a pane's, a column's, a pane's
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig
index 4370d401..0b6f04a4 100644
--- a/src/ninep/pane.zig
+++ b/src/ninep/pane.zig
@@ -50,9 +50,11 @@ pub const State = struct {
event_reader: bool = false,
events: events.Queue = .{},
nomark: bool = false,
- /// The revision a truncation of `data`, `xdata` or `body` left: the
- /// write that follows it at once joins its undo step, so a shell's
- /// `printf x > data` is one step, as acme's replace is.
+ /// The revision a truncation or a write of `data`, `xdata` or `body`
+ /// left: the write that follows it at once joins its undo step, until
+ /// that open is released (tree.zig releaseHandle). So a shell's
+ /// `printf 'x\ny\n' > data` is one step, as acme's replace is, though
+ /// bash writes it a line at a time; any other edit between breaks it.
joined: ?u32 = null,
noscroll: bool = false,
tag_snap: std.ArrayList(u8) = .empty,
@@ -454,6 +456,7 @@ fn writeBody(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply {
const at = bodyOf(pane).len;
const take = spliceBody(p, pane, at, at, req.data) orelse
return Reply.fail(req.tag, E.NOMEM);
+ pane.fs.joined = fileOf(pane).?.revision;
if (!pane.fs.noscroll) showOffset(pane, at + take);
return .{ .tag = req.tag, .written = @intCast(take) };
}
@@ -508,6 +511,7 @@ fn writeData(p: *Pardes, req: Req, pane: *Pane) Reply {
const before = dotOf(pane);
const take = spliceBody(p, pane, q0, q1, req.data) orelse
return Reply.fail(req.tag, E.NOMEM);
+ pf.joined = fileOf(pane).?.revision;
setDot(pane, .{
.q0 = shiftOne(before.q0, clip(q0), clip(q1 - q0), clip(take)),
.q1 = shiftOne(before.q1, clip(q0), clip(q1 - q0), clip(take)),
@@ -725,6 +729,7 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status {
const pf = &pane.fs;
switch (file) {
.body => if (fileOf(pane) != null) {
+ pf.joined = null;
_ = spliceBody(p, pane, 0, bodyOf(pane).len, "") orelse return .err;
pf.joined = fileOf(pane).?.revision;
pf.addr = .{};
@@ -735,6 +740,7 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status {
const q0 = pf.addr.q0;
const q1 = @max(q0, pf.addr.q1);
const before = dotOf(pane);
+ pf.joined = null;
_ = spliceBody(p, pane, q0, q1, "") orelse return .err;
pf.joined = fileOf(pane).?.revision;
setDot(pane, .{ .q0 = shiftOne(before.q0, q0, q1 - q0, 0), .q1 = shiftOne(before.q1, q0, q1 - q0, 0) });
@@ -1030,6 +1036,25 @@ test "the flag files read and write the state acme kept behind ctl verbs" {
try testing.expectEqual(E.INVAL, wr(term, Node.of(serialOf(term), .dirty), "1").errno());
}
+test "the writes of one open are one undo step, as bash's line-buffered printf makes them; the next open is another" {
+ const p = try withFile(testing.allocator, "one\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ for ([_]u64{ Node.of(serial, .data), Node.of(serial, .body), Node.of(serial, .xdata) }) |node| {
+ _ = wr(p, Node.of(serial, .addr), "$");
+ for ([_][]const u8{ "x\n", "y\n", "z\n" }) |piece| try testing.expectEqual(Status.ok, wr(p, node, piece).reply.status);
+ _ = call(p, .{ .tag = 3, .op = .release, .node = node });
+ // A second open: its own step.
+ _ = wr(p, Node.of(serial, .addr), "$");
+ try testing.expectEqual(Status.ok, wr(p, node, "w\n").reply.status);
+ _ = call(p, .{ .tag = 3, .op = .release, .node = node });
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .ctl), "Undo").reply.status);
+ try testing.expectEqualStrings("one\nx\ny\nz\n", rd(p, Node.of(serial, .body), 0, 64).bytes);
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .ctl), "Undo").reply.status);
+ try testing.expectEqualStrings("one\n", rd(p, Node.of(serial, .body), 0, 64).bytes);
+ }
+}
+
test "a replace through data, truncate then write, is one undo step" {
const p = try withFile(testing.allocator, "one two\n");
defer p.deinit();
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index 64089c01..a8b86a96 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -831,6 +831,10 @@ fn releaseHandle(p: *Pardes, req: Req) void {
p.panes[id].?.fs.tag_rewrite = false;
p.panes[id].?.fs.tag_held_newline = false;
};
+ // The writes of one open are one undo step (pane.zig `joined`).
+ 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| {
+ p.panes[id].?.fs.joined = null;
+ };
// `: > 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;