diff options
| author | Gabriel Schneider <[email protected]> | 2026-10-01 04:52:47 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 04:52:47 -0300 |
| commit | e7aaeca8e12887e2b90aae257c41efce6847c26d (patch) | |
| tree | 8e79ef09fe7060fbc0174cf76d15ca61bf8772d4 | |
| parent | 3f328a0646268169012c8c0a36840ffaba34823b (diff) | |
| download | pardes-e7aaeca8e12887e2b90aae257c41efce6847c26d.tar.gz pardes-e7aaeca8e12887e2b90aae257c41efce6847c26d.zip | |
A command pane an exec open was answered is leased to it while that open stays open: another client's command in the same directory gets a pane of its own
A finished command pane was the next command's for its directory, from
any client, so with four clients scripting one session, one read
another's output as its own last run. An open of a look or exec file
whose write was answered a pane's serial now holds that pane: reuse
passes over it and a new command pane runs the line. The open's own
next command may run there again, and once it is closed the directory's
one command pane is reused as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | src/exec.zig | 16 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 28 | ||||
| -rw-r--r-- | src/ninep/tree.zig | 3 |
3 files changed, 45 insertions, 2 deletions
diff --git a/src/exec.zig b/src/exec.zig index 47484105..ca48bed1 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -766,10 +766,13 @@ fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize { // would hang that job up. // From a column's tag, only a finished pane in that column is reused, // as the new one goes in that column (placeNew). - const reuse: ?usize = if (!p.exec_header and p.exec_column == null and src != null and src.?.command != null and src.?.command_done and !src.?.command_pty) from else for (p.panes, 0..) |slot, i| { + // Nor one leased: an open that was answered its serial and is still + // open reads that pane's output, which another client's command must + // not replace; a new pane runs it instead. + const reuse: ?usize = if (!p.exec_header and p.exec_column == null and src != null and src.?.command != null and src.?.command_done and !src.?.command_pty and !leased(p, src.?.serial)) from else for (p.panes, 0..) |slot, i| { const other = slot orelse continue; if (p.exec_column) |c| if (if (layout.findPane(p, i)) |f| f.col != c else true) continue; - if (other.command != null and other.command_done and !other.command_pty and std.mem.eql(u8, other.cwdSlice(), dir)) break i; + if (other.command != null and other.command_done and !other.command_pty and std.mem.eql(u8, other.cwdSlice(), dir) and !leased(p, other.serial)) break i; } else null; if (reuse) |id| { const pane = p.panes[id].?; @@ -812,6 +815,15 @@ fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize { return free; } +/// Whether an open of a look or exec file holds `serial` from its own write +/// (tree.Open.results): its client is reading that pane. +fn leased(p: *const Pardes, serial: u32) bool { + for (p.fs.opens) |o| if (o.node != 0 and o.results.wrote) { + if (std.mem.indexOfScalar(u32, o.results.list[0..o.results.len], serial) != null) return true; + }; + return false; +} + /// The serial a command's `$winid` names: the pane it was run from, none /// (0) from a column's or the workspace's tag. fn winid(p: *const Pardes, src: ?*Pane) u32 { diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index bcf9ffd6..c88eb328 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -4028,3 +4028,31 @@ test "pager answers its +Pager on the open that asked, whatever another client e // One directory a write. try testing.expectEqual(E.INVAL, wr(p, pager, "/a\n/b\n").errno()); } + +test "a command pane an exec open was answered is leased to it while it stays open: another client's command gets a pane of its own" { + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + const exec = Node.of(serialOf(p), .exec); + const Client = struct { + fn run(pp: *Pardes, node: u64, h: u32, line: []const u8) !usize { + try testing.expectEqual(Status.ok, call(pp, .{ .tag = 2, .op = .write, .node = node, .handle = h, .data = line }).reply.status); + const id = pp.paneBySerial(pp.fs.results[0]).?; + // Its command finishes and its output ends. + pp.panes[id].?.command_done = true; + pp.panes[id].?.command_pty = false; + return id; + } + }; + const a = call(p, .{ .tag = 1, .op = .open, .node = exec, .omode = 2 }).reply.handle; + const b = call(p, .{ .tag = 1, .op = .open, .node = exec, .omode = 2 }).reply.handle; + const a_pane = try Client.run(p, exec, a, "echo a\n"); + const b_pane = try Client.run(p, exec, b, "echo b\n"); + try testing.expect(a_pane != b_pane); + // Each one's next command runs in its own pane again. + try testing.expectEqual(a_pane, try Client.run(p, exec, a, "echo a2\n")); + try testing.expectEqual(b_pane, try Client.run(p, exec, b, "echo b2\n")); + // Closed, a lease ends: the directory's one command pane is reused. + _ = call(p, .{ .tag = 3, .op = .release, .node = exec, .handle = a, .opened = true }); + try testing.expectEqual(Status.ok, wr(p, exec, "echo c\n").reply.status); + try testing.expectEqual(a_pane, p.paneBySerial(p.fs.results[0]).?); +} diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index 9c3e1e9c..e5d1c848 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -1212,6 +1212,9 @@ fn write(p: *Pardes, req: Req, target: Target) Reply { o.pending.shrinkRetainingCapacity(rest); var w = req; w.data = whole; + // The command pane this open was answered holds no lease against its + // own next command (exec.leased): that one may run there again. + if (resultsFile(target)) o.results.len = 0; var reply = writeNow(p, w, target); if (resultsFile(target)) { @memcpy(o.results.list[0..p.fs.results_len], p.fs.results[0..p.fs.results_len]); |
