diff options
| author | Gabriel Schneider <[email protected]> | 2026-10-01 04:00:32 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 04:00:32 -0300 |
| commit | 8dc84e6f9f1c3802f1d28de4d49907381cdf224b (patch) | |
| tree | 3ed7727a61de9ae4c473413d81de3ac95844af78 /src/ninep/ctl.zig | |
| parent | a4e17d350a7afdeee4916584c44c4770243c84e4 (diff) | |
| download | pardes-8dc84e6f9f1c3802f1d28de4d49907381cdf224b.tar.gz pardes-8dc84e6f9f1c3802f1d28de4d49907381cdf224b.zip | |
pardes - asks /pager for its +Pager and reads the serial on the same open: another client's exec meanwhile can never take the text into its pane
pardes - wrote `pager <dir>` to /ctl, then read /exec on a fresh open,
which answers the session's last answer: a client that exec'd in
between had its pane overwritten by the paged text. /pager takes the
directory and answers the +Pager's serial on the open that wrote it,
as /pane/new answers its own open; the ctl verb is gone. Client.ask
writes and reads on one open. The README's look recipe reads its
answer on its own open too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/ctl.zig')
| -rw-r--r-- | src/ninep/ctl.zig | 53 |
1 files changed, 41 insertions, 12 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 42b88b91..bcf9ffd6 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -640,6 +640,23 @@ pub fn readRoot(p: *Pardes, req: Req) Reply { return tree.stagedReply(p, req); } +/// /pager, `pardes -`'s: the directory written (empty, or not absolute: the +/// session's) gets its one +Pager, made or emptied; the serial is what a +/// read of this same open answers, never another client's. +pub fn writePager(p: *Pardes, req: Req) Reply { + var it = Messages.init(p, req.data); + const line = it.next() orelse ""; + if (it.next() != null) return tree.failText(req.tag, E.INVAL, "pager: one directory a write"); + p.fs.results_len = 0; + const id = pardes.panes.Output.openPager(p, p.active, std.mem.trim(u8, line, " \t")) catch |err| + return tree.failText(req.tag, if (err == error.NoPaneRoom or err == error.NoPaneSlots) E.NOSPC else E.IO, switch (err) { + error.NoPaneRoom, error.NoPaneSlots => "pager: no space for a +Pager pane", + else => "pager: could not make a +Pager pane", + }); + noteResult(p, p.panes[id].?.serial); + return .{ .tag = req.tag, .written = @intCast(req.data.len) }; +} + /// One session builtin a line, at whichever pane has the keyboard as each /// runs, as the root's look and exec click there; all are checked first. pub fn writeRoot(p: *Pardes, req: Req) Reply { @@ -648,18 +665,6 @@ pub fn writeRoot(p: *Pardes, req: Req) Reply { var it = Messages.init(p, req.data); while (it.next()) |line| { if (line.len == 0 or exec_line.isComment(line)) continue; - // `pardes -`'s: the directory's +Pager made or emptied, its - // serial what /exec reads back. - if (std.mem.startsWith(u8, line, "pager ") or std.mem.eql(u8, line, "pager")) { - if (!apply) continue; - const id = pardes.panes.Output.openPager(p, p.active, std.mem.trim(u8, line["pager".len..], " \t")) catch |err| - return tree.failText(req.tag, if (err == error.NoPaneRoom or err == error.NoPaneSlots) E.NOSPC else E.IO, switch (err) { - error.NoPaneRoom, error.NoPaneSlots => "pager: no space for a +Pager pane", - else => "pager: could not make a +Pager pane", - }); - noteResult(p, p.panes[id].?.serial); - continue; - } if (std.mem.startsWith(u8, line, "size ") or std.mem.eql(u8, line, "size")) { // The offending word is the value: `size 5 5` quotes "5 5". const value = std.mem.trim(u8, line["size".len..], " \t"); @@ -3999,3 +4004,27 @@ test "a refused Delcol changes nothing: no pane, no focus" { try testing.expectEqual(active_column, p.active_column); try testing.expect(th.logHas(p, "unsaved ")); } + +test "pager answers its +Pager on the open that asked, whatever another client exec'd meanwhile" { + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + const pager = @intFromEnum(tree.TopFile.pager); + const exec = @intFromEnum(tree.TopFile.exec); + const asked = call(p, .{ .tag = 1, .op = .open, .node = pager, .omode = 2 }).reply.handle; + try testing.expectEqual(Status.ok, call(p, .{ .tag = 2, .op = .write, .node = pager, .handle = asked, .data = "/tmp/pager-race\n" }).reply.status); + const paged = for (p.panes) |slot| { + const pn = slot orelse continue; + if (pn.file) |f| if (std.mem.eql(u8, f.path, "/tmp/pager-race/+Pager")) break pn.serial; + } else return error.NoPager; + // Another client execs between the request and its read: a pane of its own. + const racer = call(p, .{ .tag = 3, .op = .open, .node = exec, .omode = 2 }).reply.handle; + try testing.expectEqual(Status.ok, call(p, .{ .tag = 4, .op = .write, .node = exec, .handle = racer, .data = "New\n" }).reply.status); + var line: [16]u8 = undefined; + const want = try std.fmt.bufPrint(&line, "{d}\n", .{paged}); + // A fresh open reads the session's last answer, the racer's pane: what + // `pardes -` once read, and wrote its text into. + try testing.expect(!std.mem.eql(u8, want, rd(p, exec, 0, 64).bytes)); + try testing.expectEqualStrings(want, call(p, .{ .tag = 5, .op = .read, .node = pager, .handle = asked, .size = 64 }).bytes); + // One directory a write. + try testing.expectEqual(E.INVAL, wr(p, pager, "/a\n/b\n").errno()); +} |
