diff options
Diffstat (limited to 'src/pardes.zig')
| -rw-r--r-- | src/pardes.zig | 43 |
1 files changed, 39 insertions, 4 deletions
diff --git a/src/pardes.zig b/src/pardes.zig index e92fb3f7..7863e272 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1361,6 +1361,36 @@ test "Exec in a terminal whose tty is taken runs as a command pane instead of ty try std.testing.expect(spawned); } +test "a spawn is for the pane it was made for, not whatever took the slot since" { + const gpa = std.testing.allocator; + const p = try Pardes.init(gpa, .{ .tty_only = true }); + defer p.deinit(); + while (p.nextEffect()) |_| {} + const Count = struct { + var spawns: usize = 0; + var command: [32]u8 = undefined; + var core: *Pardes = undefined; + const vtable: Host.VTable = .{ .spawn = spawn }; + fn spawn(_: ?*anyopaque, pane: u8, _: []const u8) void { + spawns += 1; + const line = core.panes[pane].?.command.?; + @memcpy(command[0..line.len], line); + } + }; + Count.core = p; + p.host = .{ .vtable = &Count.vtable }; + // A command pane closed and its slot taken, in one step, before the + // effects run: only the second's spawn is performed. + const slot = p.freeSlot().?; + _ = try p.newCommand(slot, "", "first"); + try p.removePane(slot, null); + try std.testing.expectEqual(slot, p.freeSlot().?); + _ = try p.newCommand(slot, "", "second"); + while (p.nextEffect()) |effect| p.perform(effect); + try std.testing.expectEqual(@as(usize, 1), Count.spawns); + try std.testing.expectEqualStrings("second", Count.command[0.."second".len]); +} + test "Tty+fish, one word a tag can hold, opens a terminal on that shell" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); defer p.deinit(); @@ -3136,7 +3166,10 @@ pub const WatchMode = enum { reconcile, baseline_disk }; pub const effect_path_cap = 256; pub const Effect = union(enum) { - spawn: struct { pane: u8, cwd: Buf(effect_path_cap) }, + /// A child for the pane in slot `pane` -- the one whose serial is + /// `serial`: a pane closed and its slot taken before the effect is + /// performed is not the one it was for (perform). + spawn: struct { pane: u8, serial: u32, cwd: Buf(effect_path_cap) }, write: struct { pane: u8, bytes: Buf(64) }, resize_pty: struct { pane: u8, cols: u16, rows: u16 }, signal_pty: struct { pane: u8, sig: PtySignal }, @@ -4243,7 +4276,7 @@ pub const Pardes = struct { const pane = try panes.Terminal.create(p.gpa, p.screen_w, p.screen_h); panes.Terminal.armShellSpawn(pane); p.installPane(id, pane); - p.emit(.{ .spawn = .{ .pane = @intCast(id), .cwd = .from(cwd) } }); + p.emit(.{ .spawn = .{ .pane = @intCast(id), .serial = pane.serial, .cwd = .from(cwd) } }); return pane; } @@ -4262,7 +4295,7 @@ pub const Pardes = struct { // directory's commands reuse it; the child's own cd does not move it. pane.setOwnedCwd(cwd) catch {}; p.installPane(id, pane); - p.emit(.{ .spawn = .{ .pane = @intCast(id), .cwd = .from(cwd) } }); + p.emit(.{ .spawn = .{ .pane = @intCast(id), .serial = pane.serial, .cwd = .from(cwd) } }); return pane; } @@ -4608,7 +4641,9 @@ pub const Pardes = struct { pub fn perform(p: *Pardes, e: Effect) void { const v = p.host.vtable; switch (e) { - .spawn => |s| if (v.spawn) |f| f(p.host.ctx, s.pane, s.cwd.slice()) else { + // The host reads the pane (its command, its shell) as it forks, + // so the slot must still hold the pane the spawn was for. + .spawn => |s| if (if (p.panes[s.pane]) |pane| pane.serial != s.serial else true) {} else if (v.spawn) |f| f(p.host.ctx, s.pane, s.cwd.slice()) else { p.fallback.spawned[s.pane] = true; }, // A pane with no child is silent: nothing invents output on its |
