diff options
| -rw-r--r-- | .agents/skills/pardes-9p/SKILL.md | 8 | ||||
| -rw-r--r-- | docs/fs.md | 14 | ||||
| -rw-r--r-- | src/fs-help.txt | 2 | ||||
| -rw-r--r-- | src/ninep/addr.zig | 25 | ||||
| -rw-r--r-- | src/ninep/pane.zig | 19 |
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), @@ -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 => {}, |
