diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 04:58:30 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | 9126dbe7a09d463c4eac99b8cc56b14a033b291a (patch) | |
| tree | 3266afcdd6515536335f597d67f5252eef00412b /src | |
| parent | 7f29aaf32835547ec58fc3fe0c00376f00e9eab9 (diff) | |
| download | pardes-9126dbe7a09d463c4eac99b8cc56b14a033b291a.tar.gz pardes-9126dbe7a09d463c4eac99b8cc56b14a033b291a.zip | |
follow new: a log follower that starts at the end
A follower read the ring's whole history before anything new, and there
was no way to skip it. Writing follow new instead of follow skips what the
open froze and waits for what comes after (tail -n0 -f); fs.md and the
skill say that follow replays first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/ninep/events.zig | 22 |
1 files changed, 21 insertions, 1 deletions
diff --git a/src/ninep/events.zig b/src/ninep/events.zig index a3f8c278..1067b60e 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -371,7 +371,12 @@ pub fn readLog(p: *Pardes, req: Req) Reply { /// records instead of ending; closing it is the way back. pub fn writeLog(p: *Pardes, req: Req) Reply { const slot = &(tree.openOf(p, req) orelse return Reply.fail(req.tag, E.INVAL)).what.log; - if (!std.mem.eql(u8, std.mem.trim(u8, req.data, " \t\r\n"), "follow")) return Reply.fail(req.tag, E.INVAL); + const word = std.mem.trim(u8, req.data, " \t\r\n"); + // `follow` reads what the open froze, then waits; `follow new` skips + // what was there and waits for what comes after, as tail -n0 -f does. + if (std.mem.eql(u8, word, "follow new")) { + slot.pos = slot.bytes.len; + } else if (!std.mem.eql(u8, word, "follow")) return Reply.fail(req.tag, E.INVAL); slot.follow = true; return .{ .tag = req.tag, .written = @intCast(req.data.len) }; } @@ -1110,3 +1115,18 @@ test "a click in the body reports the offsets of the word it takes, so the recor _ = call(p, .{ .tag = 3, .op = .release, .node = event, .handle = h }); } } + +test "follow new skips what the log held and waits for what comes after" { + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + p.update(.tick); // the pane's `new` is logged first + while (p.nextEffect()) |_| {} + p.setMessage(0, "before the open"); + const log = @intFromEnum(tree.TopFile.log); + const h = call(p, .{ .tag = 1, .op = .open, .node = log }).reply.handle; + try testing.expectEqual(Status.ok, call(p, .{ .tag = 2, .op = .write, .node = log, .handle = h, .data = "follow new\n" }).reply.status); + try testing.expectEqual(Status.again, call(p, .{ .tag = 3, .op = .read, .node = log, .handle = h, .size = 4096 }).reply.status); + p.setMessage(0, "after it"); + try testing.expect(std.mem.endsWith(u8, call(p, .{ .tag = 4, .op = .read, .node = log, .handle = h, .size = 4096 }).bytes, " after it\n")); + _ = call(p, .{ .tag = 5, .op = .release, .node = log, .handle = h }); +} |
