summaryrefslogtreecommitdiff
path: root/src/ninep/ctl.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 02:11:15 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit3db73735e9a8d88878bc9ee343f226c033320913 (patch)
tree93c52cd0d4d2008d765c862651464684e15006d6 /src/ninep/ctl.zig
parentb29de1d4e78466a15f5d3dc0916410cba1445c5f (diff)
downloadpardes-3db73735e9a8d88878bc9ee343f226c033320913.tar.gz
pardes-3db73735e9a8d88878bc9ee343f226c033320913.zip
A command line cut across writes runs once whole, and look/exec answer per open
A mount cuts a write at its message size, and each piece ran on its own: a line cut at the boundary ran as shell commands and a 50 KB Edit block broke. An open of look, exec, tagexec, any ctl or a column's exec now keeps an unfinished last line, or an Edit block still open, until its next write or its release, and never runs a fragment. The same open record holds what its last write touched, which a read on it answers (as /net/tcp/clone does); an open that never wrote reads the session's last as of its open. A release that runs a held line waits for quiet and counts as a change; a clicked line with a control character is still refused at its write. Tested in unit tests, fs.py (python client, 8 KiB) and selfmount.py (the kernel mount, a 1000-line seq burst and a 50 KB Edit block). Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/ctl.zig')
-rw-r--r--src/ninep/ctl.zig36
1 files changed, 34 insertions, 2 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index c3c70667..0f22614c 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -122,6 +122,28 @@ const Messages = struct {
}
};
+/// How much of `data` is whole messages, as `Messages` reads them: lines
+/// ended by a newline, an Edit block only once it closes. The rest waits
+/// for more (tree.Open.pending).
+pub fn completeEnd(p: *Pardes, data: []const u8) usize {
+ const sam = @import("../sam_edit.zig");
+ const edit = "Edit";
+ var whole: usize = 0;
+ var at: usize = 0;
+ while (std.mem.indexOfScalarPos(u8, data, at, '\n')) |nl| {
+ const start = at;
+ var end = nl;
+ const first = std.mem.trim(u8, data[start..end], " \t\r");
+ if (std.mem.startsWith(u8, first, edit) and (first.len == edit.len or first[edit.len] == ' ' or first[edit.len] == '\t')) {
+ while (sam.needsMore(p.scratch.allocator(), std.mem.trim(u8, data[start..end], " \t\r")[edit.len..]))
+ end = std.mem.indexOfScalarPos(u8, data, end + 1, '\n') orelse return whole;
+ }
+ at = end + 1;
+ whole = at;
+ }
+ return whole;
+}
+
// ---- look and exec ----
/// One command per line, checked whole before any line runs: a line is the
@@ -161,11 +183,21 @@ pub fn resultsLen(p: *Pardes) u64 {
return n;
}
+/// The panes the open's last write touched (tree.Open.results); with no
+/// open, the session's last.
pub fn readResults(p: *Pardes, req: Req) Reply {
const out = p.fs.stage(p.gpa);
- for (p.fs.results[0..p.fs.results_len]) |serial|
+ const o = tree.openOf(p, req);
+ const kept = if (o) |open| open.results.list[0..open.results.len] else p.fs.results[0..p.fs.results_len];
+ for (kept) |serial|
out.print(p.gpa, "{d}\n", .{serial}) catch return Reply.fail(req.tag, E.NOMEM);
- return tree.stagedReply(p, req);
+ const open = o orelse return tree.stagedReply(p, req);
+ if (!open.results.wrote) return tree.stagedReply(p, req);
+ var at = req;
+ at.off = open.results.read;
+ const reply = tree.stagedReply(p, at);
+ open.results.read += reply.payload.staged;
+ return reply;
}
// ---- /status ----