summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 18:28:19 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit805197e5955a86afbb47bdb482a9ebb7cfa1d171 (patch)
treec3cb3cbe6a9677f86df9b6ca4858c8bcb36e60a4 /src
parentd572edefe32284db343259b0b7e12c9470366b28 (diff)
downloadpardes-805197e5955a86afbb47bdb482a9ebb7cfa1d171.tar.gz
pardes-805197e5955a86afbb47bdb482a9ebb7cfa1d171.zip
The docs say how a --detach=NAME session is posted, what > does to tag, where #n inside a character lands, and what tag click offsets count; a refusal says which ctl takes the word, and a lock refusal who holds it
Dogfood round 4's doc list: the recipe assumed pardes/<pid>; the README said tag writes append, though > replaces the editable text; #n inside a multibyte rune or grapheme snaps to its start, unsaid; a tag click's offsets count the path. A word written to the other ctl now says write it to pane/<n>/ctl or /ctl, and a held lock says another open of that ctl holds it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/fs-help.txt6
-rw-r--r--src/ninep/ctl.zig21
2 files changed, 18 insertions, 9 deletions
diff --git a/src/fs-help.txt b/src/fs-help.txt
index 2ae4f360..214986a1 100644
--- a/src/fs-help.txt
+++ b/src/fs-help.txt
@@ -9,14 +9,14 @@ log events: new/del/rename/save <serial> <name>, run/exit/send <serial>
screen rendered screen as JSON, frozen from open to close
listeners the session's dial addresses
focus the serial of the pane with the keyboard; write a serial to give it the keyboard
-ctl settings, one a line; or Newcol, Dump, Exit (quits; asks once), Kill [word] (stops what pardes ran, or what starts with word)
+ctl settings, one a line; or Newcol, Dump, Exit (quits, asks once), Kill [w] (stops what pardes ran, w: by first word)
commands every builtin: its word, arg if it takes one, and root or pane: which ctl takes it
pane/new open it to make a pane (named +New); the read answers that pane's serial
pane/<n>/ name body tag ctl addr dot limit data xdata sel dirty mark scroll errors (write-only:
appended to its directory's +Errors pane) event look exec, pty/ on terminals; rmdir closes
os/, src/ the host filesystem; the editor's own sources (-Dembed-sources=true builds only)
-Below, $m is the mount: $NINE_MOUNT/pardes/<pid> when 9ns --mntgen set $NINE_MOUNT; PARDES_MOUNT in a Tty9p shell.
+Below, $m is the mount: $NINE_MOUNT/pardes/<pid, or NAME of --detach=NAME>; PARDES_MOUNT in a Tty9p shell.
cat $m/index which panes exist
n=$(cat $m/pane/new) make a pane, take its serial
@@ -37,7 +37,7 @@ Pitfalls, one each:
Each open of pane/new makes another pane (ls, stat, find never do); rmdir closes even a dirty one.
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.
+ tag reads the path, then its own text; > replaces that text (default words too), >> appends.
A terminal's body is a history snapshot frozen per open; pty/data is the live stream.
exec: a non-builtin line runs as a command pane (ctl's Shell -c; exit N shown, logged), a shell's typed in.
Through a mount a malformed write is EINVAL, a well-formed one that fails EIO: log's err says why.
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 363524f1..d7d50cc6 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -208,6 +208,13 @@ fn refuse(p: *Pardes, req: Req, why: []const u8, line: []const u8) Reply {
return tree.failText(req.tag, E.INVAL, text);
}
+/// `refuse`, saying which ctl takes the message instead.
+fn refuseTo(p: *Pardes, req: Req, why: []const u8, line: []const u8, ctl: []const u8) Reply {
+ const room = p.fs.ename.len -| (why.len + ctl.len + 18);
+ const text = std.fmt.bufPrint(&p.fs.ename, "{s} \"{s}\": write it to {s}", .{ why, line[0..@min(line.len, room)], ctl }) catch why;
+ return tree.failText(req.tag, E.INVAL, text);
+}
+
/// Checks one line written to a ctl as a builtin of `scope` before any line
/// of the write runs: a word the registry knows, of this ctl's scope, given
/// an argument if and only if it takes or requires one, and for a setting a
@@ -220,7 +227,7 @@ fn checkBuiltin(p: *Pardes, req: Req, line: []const u8, scope: builtins.Scope) ?
const arg = if (sp) |s| std.mem.trim(u8, line[s + 1 ..], " \t") else "";
const b = std.meta.stringToEnum(Builtin, word) orelse return refuse(p, req, "unknown control message", line);
if (builtins.registry.scope(b) != scope)
- return refuse(p, req, if (scope == .pane) "not a window control message" else "not a session control message", line);
+ return refuseTo(p, req, if (scope == .pane) "not a window control message" else "not a session control message", line, if (scope == .pane) "/ctl" else "pane/<n>/ctl");
if (arg.len > 0 and !builtins.registry.takesArg(b)) return refuse(p, req, "wrong #args in control message", line);
if (arg.len == 0 and builtins.registry.requiresArg(b)) return refuse(p, req, "wrong #args in control message", line);
const setting = config.Runtime.find(word) orelse return null;
@@ -459,7 +466,7 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply {
if (errno != 0) return Reply.fail(req.tag, errno);
} else if (std.mem.eql(u8, line, "lock")) {
if (mine == null) return tree.failText(req.tag, E.INVAL, tree.e_bad_ctl);
- if (other) return tree.failText(req.tag, E.BUSY, tree.e_in_use);
+ if (other) return tree.failText(req.tag, E.BUSY, e_locked);
held = true;
if (apply) pane.fs.lock = mine;
} else if (std.mem.eql(u8, line, "unlock")) {
@@ -477,6 +484,8 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply {
}
const e_not_locked = "window not locked by this open";
+/// Who holds it is another open of this ctl, whose client pardes cannot name.
+const e_locked = tree.e_in_use ++ ": another open of this ctl holds the lock";
/// acme's get asks winclean first (exec.c:513): unsaved text is warned
/// about once, and the same get again, nothing edited since, discards it.
@@ -635,8 +644,8 @@ test "the pane ctl takes acme's verbs and the pane's builtins, and refuses the r
var want: [64]u8 = undefined;
try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "unknown control message \"{s}\"", .{bad}), refused.reply.ename);
}
- try testing.expectEqualStrings("not a window control message \"Newcol\"", wr(p, ctl_node, "Newcol").reply.ename);
- try testing.expectEqualStrings("not a window control message \"Verbose off\"", wr(p, ctl_node, "Verbose off").reply.ename);
+ try testing.expectEqualStrings("not a window control message \"Newcol\": write it to /ctl", wr(p, ctl_node, "Newcol").reply.ename);
+ try testing.expectEqualStrings("not a window control message \"Verbose off\": write it to /ctl", wr(p, ctl_node, "Verbose off").reply.ename);
try testing.expectEqualStrings("wrong #args in control message \"Collapse now\"", wr(p, ctl_node, "Collapse now").reply.ename);
// Checked whole first: a bad line anywhere and nothing ran.
try testing.expectEqual(E.INVAL, wr(p, ctl_node, "Msg first\nbogus").errno());
@@ -688,7 +697,7 @@ test "the root ctl reads the settings as a write takes them, and takes the sessi
try testing.expectEqual(Status.ok, wr(p, root_ctl, "Newcol\n").reply.status);
try testing.expectEqual(columns + 1, p.ncol);
- try testing.expectEqualStrings("not a session control message \"Del\"", wr(p, root_ctl, "Del").reply.ename);
+ try testing.expectEqualStrings("not a session control message \"Del\": write it to pane/<n>/ctl", wr(p, root_ctl, "Del").reply.ename);
try testing.expectEqualStrings("unknown control message \"Nonsense 1\"", wr(p, root_ctl, "Nonsense 1").reply.ename);
try testing.expectEqualStrings("bad value in control message \"Verbose maybe\"", wr(p, root_ctl, "Verbose maybe").reply.ename);
const no_theme = wr(p, root_ctl, "Theme no-such-theme").reply.ename;
@@ -731,7 +740,7 @@ test "a second lock is refused until the holder unlocks or closes, and binds nob
try testing.expectEqual(Status.ok, w.ctl(p, ctl_node, a, "lock\n").reply.status);
// Another open's lock is refused at once, and so is its unlock; writes
// to the pane's other files are not.
- try testing.expectEqualStrings(tree.e_in_use, w.ctl(p, ctl_node, b, "lock\n").reply.ename);
+ try testing.expectEqualStrings(e_locked, w.ctl(p, ctl_node, b, "lock\n").reply.ename);
try testing.expectEqual(E.BUSY, w.ctl(p, ctl_node, b, "lock\n").errno());
try testing.expectEqualStrings(e_not_locked, w.ctl(p, ctl_node, b, "unlock\n").reply.ename);
try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .addr), "1").reply.status);