From 94f6090603e9c9f611a01335bd4eca480b00db76 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 08:44:40 -0300 Subject: `pardes --wait FILE` with $PARDES_9P set but no pane of its own (an agent's or a script's EDITOR) asks that session and waits there, not a screen of its own Forwarding asked for $PARDES_PID and $PARDES_PANE, which only a pane's shell has, so a client that talks to a session from outside its panes got a full editor drawn in its terminal. With --wait and $PARDES_9P alone, the file is looked at through the session's root look and the wait is for that pane's del. Co-Authored-By: Claude Opus 5.5 --- src/main.zig | 20 +++++++++++++++----- test/fs.py | 16 ++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/main.zig b/src/main.zig index 5dd6a8e6..e998accc 100644 --- a/src/main.zig +++ b/src/main.zig @@ -563,16 +563,23 @@ fn nativeMain(init: std.process.Init) !void { // process is a shell that outlived its editor (a daemon started from a // pane, a stale environment carried into a new terminal), which is not // nested at all. - const owner = std.mem.span(std.c.getenv("PARDES_PID") orelse break :forwarding); - const pid = std.fmt.parseInt(std.c.pid_t, owner, 10) catch break :forwarding; - if (pid <= 0 or std.c.kill(pid, @enumFromInt(0)) != 0) break :forwarding; + // `--wait` with only $PARDES_9P (a script or agent that talks to a + // session from outside its panes, EDITOR='pardes --wait') is asked + // of that session too: what it waits on is a pane there. + const outside = wait and std.c.getenv("PARDES_PID") == null and std.c.getenv("PARDES_9P") != null; + if (!outside) { + const owner = std.mem.span(std.c.getenv("PARDES_PID") orelse break :forwarding); + const pid = std.fmt.parseInt(std.c.pid_t, owner, 10) catch break :forwarding; + if (pid <= 0 or std.c.kill(pid, @enumFromInt(0)) != 0) break :forwarding; + } // Can I reach it? That is the other two, and it is a separate answer. const Parent = struct { dial: []const u8, serial: u32 }; const found: ?Parent = reaching: { const dial = std.mem.span(std.c.getenv("PARDES_9P") orelse break :reaching null); ninep_io.Client.validateDial(dial) catch break :reaching null; - const pane_text = std.mem.span(std.c.getenv("PARDES_PANE") orelse break :reaching null); + // No pane of its own (`outside`): the session's root look, 0. + const pane_text = std.mem.span(std.c.getenv("PARDES_PANE") orelse if (outside) break :reaching .{ .dial = dial, .serial = 0 } else break :reaching null); for (pane_text) |byte| if (!std.ascii.isDigit(byte)) break :reaching null; const serial = std.fmt.parseInt(u32, pane_text, 10) catch break :reaching null; if (serial == 0) break :reaching null; @@ -602,7 +609,7 @@ fn nativeMain(init: std.process.Init) !void { // The pane's `look` file: one line, and the line is the clicked text // itself, which is what a right click in that pane would have been. var look_buf: [64]u8 = undefined; - const look = try std.fmt.bufPrint(&look_buf, "/pane/{d}/look", .{parent.serial}); + const look = if (parent.serial == 0) "/look" else try std.fmt.bufPrint(&look_buf, "/pane/{d}/look", .{parent.serial}); // `pardes -`: stdin read to its end, a +Pager pane of the session // made of it (as acme's `B` has no stdin, through pane/new, a name // and the body), and back at once: the PAGER a pane's shell gets. @@ -613,6 +620,9 @@ fn nativeMain(init: std.process.Init) !void { pageInto(init.io, arena, parent.dial, text); return; }; + // From outside the session's panes there is no pane to say it is + // nested in: `--wait` with no file opens nothing there. + if (parent.serial == 0 and positional == null) break :forwarding; const typed_word = positional orelse { var tag_buf: [64]u8 = undefined; const tag = try std.fmt.bufPrint(&tag_buf, "/pane/{d}/tag", .{parent.serial}); diff --git a/test/fs.py b/test/fs.py index 270ac367..11233e80 100644 --- a/test/fs.py +++ b/test/fs.py @@ -1397,6 +1397,22 @@ def test(binary, quic=False): assert time.monotonic() - deleted < .05, time.monotonic() - deleted assert second.wait(timeout=5) == 0 assert edited.read_bytes() == b'edited\n' + # --wait from outside the session's panes, $PARDES_9P alone (an + # agent's EDITOR): asked of the session, not a screen of its own. + outside_env = {k: v for k, v in env.items() if k not in ('PARDES_PANE', 'PARDES_PID')} + far = root / 'wait far.txt' + far.write_bytes(b'far\n') + before = serials() + waiting = subprocess.Popen([binary, '--wait', str(far)], cwd=root, env=outside_env, + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) + deadline = time.monotonic() + 5 + while not serials() - before and time.monotonic() < deadline: + time.sleep(.01) + far_pane, = serials() - before + time.sleep(.3) + assert waiting.poll() is None, waiting.stderr.read() + client.remove(f'/pane/{far_pane}') + assert waiting.wait(timeout=5) == 0 # `--` ends the options: a name starting with a dash is a name. dash = root / '-dash.txt' dash.write_bytes(b'dash\n') -- cgit v1.3