summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 18:20:33 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commitafc72ac3db0840d8571dcfb8e2e182a241bdecaf (patch)
tree287d4e2cc74839e668f3afba4d13873923a40d89
parent4354669aa560c5504b046413f771d02c4216dd45 (diff)
downloadpardes-afc72ac3db0840d8571dcfb8e2e182a241bdecaf.tar.gz
pardes-afc72ac3db0840d8571dcfb8e2e182a241bdecaf.zip
A shell's > on addr no longer resets it, so echo /re/ > addr searches on
Truncating addr reset it to #0, so a shell's find-and-replace loop matched the first hit for ever, .+#1 read 1 1 each time, and foo x foo y foo became foobarbarbar... acme resets addr on the first open (xfid.c:105-108), for clients that hold the fid; a shell cannot. Here neither an open nor a truncation resets it: the address written is evaluated from the last one, and 0 or , starts over. Documented as a departure from acme. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--.agents/skills/pardes-9p/SKILL.md8
-rw-r--r--docs/fs.md14
-rw-r--r--src/fs-help.txt2
-rw-r--r--src/ninep/addr.zig25
-rw-r--r--src/ninep/pane.zig19
5 files changed, 51 insertions, 17 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md
index 6651a9db..756a9785 100644
--- a/.agents/skills/pardes-9p/SKILL.md
+++ b/.agents/skills/pardes-9p/SKILL.md
@@ -188,8 +188,12 @@ buffer differs from its file, whether a write pushes an undo point, and whether
a write scrolls. Truncating `tag` clears the part of the tag you may edit.
Address state belongs to the pane, not to a client: it keeps the last range
-written until someone writes or truncates it, so writing an address and reading
-it back evaluates it, and two clients addressing the same pane will interfere.
+written until someone writes another, so writing an address and reading it back
+evaluates it, and two clients addressing the same pane will interfere. Neither
+an open nor a `>` resets it (acme resets it on the first open): each `echo /re/
+> addr` searches on from the last address, so a find-and-replace loop advances.
+Write `0` to start from the top. The search wraps, so stop a find-all loop when
+the address comes back to where it began, or set `limit`.
`$pane/ctl` reads acme's window status line — serial, tag length, body length,
a reserved zero, the dirty flag, the width in cells, the font and the tab
width, then `current` or `notcurrent` — and takes `get` (reload from disk),
diff --git a/docs/fs.md b/docs/fs.md
index b8425d2f..7ccb9a91 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -369,8 +369,16 @@ leaves `addr` just past what it wrote, so a second `echo x > data` deletes
the empty range there and inserts after the first rather than replacing it
again; write `addr` before each replacement.
`addr` belongs to the pane rather than to a client and keeps what was written
-until someone writes or truncates it, so writing an address and reading it
-back evaluates it, which is what acme(4) promises of its own `addr`.
+until someone writes another, so writing an address and reading it back
+evaluates it, which is what acme(4) promises of its own `addr`. Unlike acme,
+neither an open nor a truncation resets it: acme sets it to `#0` when the
+first client opens `addr` (editors/acme/xfid.c:105-108), which suits a
+client that holds the fid, but a shell opens the file anew for every
+`echo /re/ > addr` and so would search from the top each time and never
+advance. Here each such write searches on from the last address, as `>>`
+does; write `0` to start again from the top. A search wraps at the end of
+the text, so a find-all loop stops when the address comes back to where it
+began, or bounds itself with `limit`.
The regular expressions are mvzr's (sets, `\d`/`\w`/`\s`, `{m,n}` and
lazy `*?` included), searched the way sam searches (editors/acme/regx.c): as
@@ -409,7 +417,7 @@ expression`, `regular expression search gave up, ...`, or sam's
`addresses out of order` for a range that ends before it starts
(`#100,#50`), which acme lets through. A failed write to `addr` leaves no
address at all, where acme
-keeps the old one: until an address is written or `addr` is truncated,
+keeps the old one: until a good address is written,
reading `addr`, and reading, writing or truncating `data` and `xdata`, fail
with `no address: the last one written to addr failed`, so a script that
missed its target cannot then write at the last one.
diff --git a/src/fs-help.txt b/src/fs-help.txt
index 57ef7b62..2ae4f360 100644
--- a/src/fs-help.txt
+++ b/src/fs-help.txt
@@ -35,7 +35,7 @@ Below, $m is the mount: $NINE_MOUNT/pardes/<pid> when 9ns --mntgen set $NINE_MOU
Pitfalls, one each:
Each open of pane/new makes another pane (ls, stat, find never do); rmdir closes even a dirty one.
- addr takes #n,#m or /regexp/ from the current address (^ $ per line); a failed one refuses data.
+ addr: #n,#m or /regexp/ from the current address (^ $ per line); > never resets it, 0 does; a miss refuses data.
data: > replaces the addr range, : > deletes it, a 2nd > inserts (addr moved); body: > empties all.
tag reads the path, then its own text; writes append; truncating clears it, default words too.
A terminal's body is a history snapshot frozen per open; pty/data is the live stream.
diff --git a/src/ninep/addr.zig b/src/ninep/addr.zig
index 0537c17a..ccb08924 100644
--- a/src/ninep/addr.zig
+++ b/src/ninep/addr.zig
@@ -340,13 +340,34 @@ test "a failed address leaves none, so data refuses rather than act at the last
try testing.expectEqualStrings(pane_files.e_addr_failed, th.rd(p, addr, 0, 64).reply.ename);
try testing.expectEqual(E.INVAL, th.call(p, .{ .tag = 1, .op = .setattr, .node = Node.of(serial, .xdata), .truncate = true }).errno());
try testing.expectEqualStrings("one\ntwo\n", p.panes[0].?.file.?.content);
- // A good address, or a truncated addr, gives it one again.
+ // A good address gives it one again; truncating addr does not.
_ = th.wr(p, addr, "#0,#3");
try testing.expectEqual(Status.ok, th.wr(p, data, "ONE").reply.status);
try testing.expectEqualStrings("ONE\ntwo\n", p.panes[0].?.file.?.content);
_ = th.wr(p, addr, "99");
_ = th.call(p, .{ .tag = 1, .op = .setattr, .node = addr, .truncate = true });
- try testing.expectEqual(Status.ok, th.wr(p, data, "0").reply.status);
+ try testing.expectEqualStrings(pane_files.e_addr_failed, th.wr(p, data, "0").reply.ename);
+}
+
+test "a shell's > on addr searches on from the last address, and 0 starts over" {
+ const p = try th.withFile(testing.allocator, "foo x foo y foo\n");
+ defer p.deinit();
+ const serial = th.serialOf(p);
+ const addr = Node.of(serial, .addr);
+ const data = Node.of(serial, .data);
+ // echo /foo/ > addr; printf foobar > data -- twice over, as a loop does.
+ for (0..3) |_| {
+ _ = th.call(p, .{ .tag = 1, .op = .setattr, .node = addr, .truncate = true });
+ try testing.expectEqual(Status.ok, th.wr(p, addr, "/foo/").reply.status);
+ _ = th.call(p, .{ .tag = 1, .op = .setattr, .node = data, .truncate = true });
+ try testing.expectEqual(Status.ok, th.wr(p, data, "foobar").reply.status);
+ }
+ try testing.expectEqualStrings("foobar x foobar y foobar\n", p.panes[0].?.file.?.content);
+ // .+#1 moves on from where the last write left the address.
+ _ = th.wr(p, addr, "0");
+ _ = th.call(p, .{ .tag = 1, .op = .setattr, .node = addr, .truncate = true });
+ try testing.expectEqual(Status.ok, th.wr(p, addr, ".+#1").reply.status);
+ try testing.expectEqualStrings(" 1 1 ", th.rd(p, addr, 0, 64).bytes);
}
test "the address language, form by form" {
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig
index f87e43a6..714a2566 100644
--- a/src/ninep/pane.zig
+++ b/src/ninep/pane.zig
@@ -29,14 +29,15 @@ const Node = tree.Node;
/// Filesystem state a pane carries beside its editor state.
pub const State = struct {
/// The range `data` and `xdata` read and write through. acme clears it
- /// when the first client opens `addr` (editors/acme/xfid.c:105), which
- /// suits a client that holds the fid open and leaves a shell reading back
- /// `0 0` from the address it just wrote. Here it is the pane's own
- /// register, cleared by truncating the file, so that `cp addr dot` and
+ /// when the first client opens `addr` (editors/acme/xfid.c:105-108),
+ /// which suits a client that holds the fid open; a shell cannot, and
+ /// would evaluate every `echo /re/ > addr` from #0 and never advance.
+ /// Here it is the pane's own register, cleared by neither an open nor
+ /// a truncation: `0` or `,` starts over, and `cp addr dot` and
/// `cat addr` answer what was written.
addr: Range = .{},
/// The last address written to `addr` failed, so there is none: `data`
- /// and `xdata` refuse until one is written or `addr` is truncated,
+ /// and `xdata` refuse until a good one is written,
/// rather than act at the address before it, which acme would do.
addr_failed: bool = false,
limit: ?Range = null,
@@ -634,10 +635,10 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status {
pane.tag.vsel.active = false;
pane.tag.nsel = 0;
},
- .addr => {
- pf.addr = .{};
- pf.addr_failed = false;
- },
+ // A shell's `>` truncates before it writes: the address written
+ // 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,
.dot => if (fileOf(pane) != null) setDot(pane, .{}),
else => {},