summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 13:22:09 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commitf4fdeb4f0f5f08699f77bd2cc6cac5a87d9f5091 (patch)
tree626e4559d420a08acd0b5ddd30b3277745402060
parent905fd5a87a94b1183779494380dfb8d3d75e2926 (diff)
downloadpardes-f4fdeb4f0f5f08699f77bd2cc6cac5a87d9f5091.tar.gz
pardes-f4fdeb4f0f5f08699f77bd2cc6cac5a87d9f5091.zip
The root look and exec read back what a ctl write touched too
After a pane made by Tty or Newcol written to a ctl, /look and /exec still read the serials of the last look or exec, and a script took the old pane for the new one. A ctl write now resets them and records what each line made, or else the pane it ran at, as a look or exec does. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--docs/fs.md3
-rw-r--r--src/ninep/ctl.zig32
2 files changed, 34 insertions, 1 deletions
diff --git a/docs/fs.md b/docs/fs.md
index 03f250af..96d0eb3a 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -82,7 +82,8 @@ Existing Plan9port/v9fs clients need a userspace bridge for QUIC.
/README this guide, also src/fs-help.txt
/index one line per pane: serial, kind (text|term|pdf|image), dirty flag, name
/status pid, version and pane count
-/look write a line: a right click on it at the active pane; read: the serials it touched
+/look write a line: a right click on it at the active pane; read: the serials the last
+ look, exec or ctl write touched (made, else acted at)
/exec write a line: a middle click; read the same serials
/log recent events, one a line: new|del|rename|save <serial> <name>, msg <serial|-> <text>,
dump|restore <path>, err <serial|-> <file>: <why>;
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 70e06d4b..382cc0e7 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -232,7 +232,18 @@ fn runBuiltin(p: *Pardes, req: Req, id: usize, line: []const u8) ?Reply {
p.fs.refused = false;
p.fs.failure_len = 0;
defer p.fs.no_prompt = false;
+ // What a ctl write touched reads back from /look and /exec as a click's
+ // does (`run`), so a script that made a pane with `Tty` finds it there.
+ const context = p.panes[id].?.serial;
+ const before = Serials.snapshot(p);
_ = exec_line.executeBuiltinLine(p, id, line);
+ const after = Serials.snapshot(p);
+ var created = false;
+ for (after.list[0..after.len]) |serial| if (std.mem.indexOfScalar(u32, before.list[0..before.len], serial) == null) {
+ noteResult(p, serial);
+ created = true;
+ };
+ if (!created) noteResult(p, context);
if (p.fs.refused) return refuse(p, req, e_prompt, line);
if (p.fs.failure_len == 0) return null;
const refusal = refuse(p, req, p.fs.failure[0..p.fs.failure_len], line);
@@ -302,6 +313,7 @@ pub fn readRoot(p: *Pardes, req: Req) Reply {
/// runs, as the root's look and exec click there; all are checked first.
pub fn writeRoot(p: *Pardes, req: Req) Reply {
for ([2]bool{ false, true }) |apply| {
+ if (apply) p.fs.results_len = 0;
var it = std.mem.splitScalar(u8, req.data, '\n');
while (it.next()) |raw| {
const line = std.mem.trim(u8, raw, " \t\r");
@@ -409,6 +421,7 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply {
// Checked whole before anything applies, so a write that must wait for
// the lock has done nothing yet when it goes again.
for ([2]bool{ false, true }) |apply| {
+ if (apply) p.fs.results_len = 0;
var held = !other and pane.fs.lock != null;
var it = std.mem.splitScalar(u8, req.data, '\n');
while (it.next()) |raw| {
@@ -894,3 +907,22 @@ test "a pane's own look and exec go to the event reader holding it, which writes
try testing.expectEqual(tree.Status.ok, wr(p, Node.of(serial, .exec), "Msg direct\n").reply.status);
try testing.expect(std.mem.indexOf(u8, pane.msg[0..pane.msg_len], "direct") != null);
}
+
+test "look and exec read back what the last command touched, a ctl write's too" {
+ const gpa = testing.allocator;
+ const p = try withFile(gpa, "x\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ var want: [32]u8 = undefined;
+ _ = wr(p, root_exec, "Msg first\n");
+ try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{serial}), rd(p, root_exec, 0, 64).bytes);
+ // A pane made through ctl is what both read next, not the Msg's pane.
+ try testing.expectEqual(tree.Status.ok, wr(p, Node.of(serial, .ctl), "Tty\n").reply.status);
+ const made = p.panes[p.active].?.serial;
+ try testing.expect(made != serial);
+ try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{made}), rd(p, root_exec, 0, 64).bytes);
+ try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{made}), rd(p, @intFromEnum(tree.TopFile.look), 0, 64).bytes);
+ // A ctl write that makes nothing names the pane it ran at.
+ try testing.expectEqual(tree.Status.ok, wr(p, @intFromEnum(tree.TopFile.ctl), "Verbose off\n").reply.status);
+ try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{made}), rd(p, root_exec, 0, 64).bytes);
+}