diff options
Diffstat (limited to 'src/tty/tty.zig')
| -rw-r--r-- | src/tty/tty.zig | 50 |
1 files changed, 46 insertions, 4 deletions
diff --git a/src/tty/tty.zig b/src/tty/tty.zig index 6083f609..822c2d7f 100644 --- a/src/tty/tty.zig +++ b/src/tty/tty.zig @@ -318,8 +318,17 @@ const Pty = struct { file: std.Io.File, pid: posix.pid_t, reader: std.Io.Future(anyerror!void), + /// A command pane's child, watched to its exit (host_io.watchExit); + /// the pty stays open until both its end of file and its exit. + cmd: host_io.CommandWatch = .{}, }; +/// A watched child exited: wake the loop, which takes it (`takeExits`). +fn wakeForExit(ctx: ?*anyopaque) void { + const loop: *Loop = @ptrCast(@alignCast(ctx.?)); + _ = loop.tryPostEvent(.nop) catch {}; +} + const KittyPlacement = struct { cell_x: u16, cell_y: u16, @@ -732,7 +741,9 @@ fn localSession( .test_clock = host_io.testClock(), }; pardes.lsp.setStatusSink(&sh, lspStatusSink); + host_io.setExitWake(loop, wakeForExit); defer { + host_io.setExitWake(null, null); pardes.lsp.setStatusSink(null, null); for (&sh.ptys) |*slot| if (slot.*) |*pt| { pt.reader.cancel(io) catch {}; @@ -805,7 +816,7 @@ fn localSession( break :blk; }; defer gpa.free(bytes); - const nc = dump.restore(core, bytes) catch |err| { + const nc = dump.restore(core, bytes, rp) catch |err| { core.reportError(core.active, "Restore", err); break :blk; }; @@ -818,6 +829,7 @@ fn localSession( for (&sh.ptys) |*slot| if (slot.*) |*pt| { pt.reader.cancel(io) catch {}; _ = libc.close(pt.file.handle); + host_io.retireShell(pt.pid); slot.* = null; }; for (0..pardes.MAX_PANES) |wid| file_watch.watchPane( @@ -991,6 +1003,9 @@ const Shell = struct { batch += 1; } if (motion) |m| _ = s.apply(m); + // An exit's wake (a .nop) or output an exit waited behind: told + // here, inside the step, so the frame after it shows it. + s.takeExits(); s.reloadWatched(); } @@ -1020,14 +1035,27 @@ const Shell = struct { if (s.gens[pr.id] == pr.gen) core.update(.{ .output = .{ .pane = @intCast(pr.id), .bytes = pr.bytes } }); s.gpa.free(pr.bytes); + // An exit waiting behind this output may be told now. + s.takeExits(); return true; }, .pty_eof => |e| if (s.gens[e.id] == e.gen) { if (s.ptys[e.id]) |*pt| { pt.reader.await(s.io) catch {}; // reader just finished; join it or its future leaks + // A command's pty stays open until its child has exited: + // closing it would hang up one that runs on without it. + // Its exit, if it came first, is told now its output is in. + if (pt.cmd.watched) { + host_io.commandEof(core, &s.ptys, e.id, s, closeWatched); + return true; + } _ = libc.close(pt.file.handle); - host_io.retireShell(pt.pid); + // Unwatched, a command's exit is read here, as its end. + const command = if (core.panes[e.id]) |pn| pn.command != null else false; + const status = if (command) host_io.exitStatus(pt.pid, 100) else null; + if (status == null) host_io.retireShell(pt.pid); s.ptys[e.id] = null; + if (command) core.update(.{ .exited = .{ .pane = @intCast(e.id), .status = status } }); } core.update(.{ .eof = .{ .pane = @intCast(e.id) } }); }, @@ -1216,7 +1244,8 @@ const Shell = struct { closePty(ctx, pane); // a shell still in the slot goes first, reaped s.gens[pane] +%= 1; const child = host_io.forkShell(s.core, pane, s.prompt_rcs, s.core.shellBin(), cwd, s.core.screen_h, s.core.screen_w, s.fs) catch |err| return s.core.reportError(pane, "shell", err); - s.ptys[pane] = .{ .file = child.file, .pid = child.pid, .reader = .{ .any_future = null, .result = {} } }; + const command = if (s.core.panes[pane]) |pn| pn.command != null else false; + s.ptys[pane] = .{ .file = child.file, .pid = child.pid, .reader = .{ .any_future = null, .result = {} }, .cmd = .{ .watched = command and host_io.watchExit(child.pid), .fd = child.file.handle } }; var lbuf: [pardes.memory.limits.host_path_cap + 1]u8 = undefined; if (host_io.shellCwd(child.pid, &lbuf)) |wd| s.core.setCwd(pane, wd); if (s.threads_ok) { @@ -1241,7 +1270,9 @@ const Shell = struct { fn ptySignal(ctx: ?*anyopaque, pane: u8, sig: pardes.PtySignal) void { const s = of(ctx); - if (s.ptys[pane]) |pt| host_io.signalTty(pt.pid, pt.file.handle, sig); + // A command whose exit is recorded is reaped: its group may be + // another's by now. + if (s.ptys[pane]) |pt| if (!pt.cmd.exited) host_io.signalTty(pt.pid, pt.file.handle, sig); } /// The pane is gone: hang its pty up, which the kernel passes on to the @@ -1263,6 +1294,17 @@ const Shell = struct { return host_io.ttyTaken(pt.pid, pt.file.handle); } + /// The command panes' exits, told once their output is in (host_io). + fn takeExits(s: *@This()) void { + host_io.takeExits(s.core, &s.ptys, s, closeWatched); + } + + fn closeWatched(s: *@This(), id: usize) void { + const pt = &(s.ptys[id] orelse return); + _ = libc.close(pt.file.handle); + s.ptys[id] = null; + } + fn killJob(ctx: ?*anyopaque, pane: u8) bool { const s = of(ctx); const pt = s.ptys[pane] orelse return false; |
