From f4fdeb4f0f5f08699f77bd2cc6cac5a87d9f5091 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 13:22:09 -0300 Subject: 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 --- src/ninep/ctl.zig | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'src/ninep') 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); +} -- cgit v1.3