diff options
Diffstat (limited to 'src/gui/gui.zig')
| -rw-r--r-- | src/gui/gui.zig | 73 |
1 files changed, 63 insertions, 10 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig index bfa83175..175db9e2 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -840,8 +840,17 @@ const Pty = struct { kill_at: i64 = 0, reader: ?std.Thread = null, stop: [2]c_int = .{ -1, -1 }, + /// 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 q: *Queue = @ptrCast(@alignCast(ctx.?)); + q.wake(); +} + const RetiredShell = struct { pid: libc.pid_t = 0, kill_at: i64 = 0 }; const Msg = union(enum) { @@ -2536,6 +2545,8 @@ fn localSession( shell.threads_ok = true; pardes.lsp.setStatusSink(&queue, lspStatusSink); defer pardes.lsp.setStatusSink(null, null); + host_io.setExitWake(&queue, wakeForExit); + defer host_io.setExitWake(null, null); while (!core.quit) { pardes.turn.restoreSettled(); @@ -2824,6 +2835,8 @@ fn runGrid(init: std.process.Init, opts_in: pardes.Options) !void { shell.threads_ok = true; pardes.lsp.setStatusSink(&queue, lspStatusSink); defer pardes.lsp.setStatusSink(null, null); + host_io.setExitWake(&queue, wakeForExit); + defer host_io.setExitWake(null, null); setStdinRaw() catch {}; // stdin may be a pipe, not a pty — best effort @@ -3865,8 +3878,29 @@ const Shell = struct { break :vt v; }; + /// Each watched child that exited: reaped, the core told, and its pty + /// closed if its end of file came first. + fn takeExits(s: *Shell) void { + while (host_io.takeExited()) |pid| for (s.ptys) |*slot| { + const pt = if (slot.*) |*pt| pt else continue; + if (pt.pid != pid or pt.cmd.exited) continue; + pt.cmd.exit((host_io.reapExited(pid) orelse break).status); + pt.pid = 0; // reaped: no signal or retire may reach whoever gets it next + break; + }; + for (s.ptys, 0..) |*slot, id| { + const pt = if (slot.*) |*pt| pt else continue; + if (!pt.cmd.due()) continue; + pt.cmd.told = true; + s.core.update(.{ .exited = .{ .pane = @intCast(id), .status = pt.cmd.status } }); + if (pt.cmd.eof) s.closePty(@intCast(id)); + s.saw_event = true; + } + } + fn drainQueue(s: *Shell) void { s.reconcilePtys(); + s.takeExits(); var msgs = s.queue.take(); var check_files = false; for (msgs.slice()) |m| switch (m) { @@ -3877,20 +3911,38 @@ const Shell = struct { s.saw_event = true; }, .eof => |e| { - if (s.gens[e.pane] == e.gen) { - // Reaped here, its status kept, it is no shell to retire. - const status = if (s.ptys[e.pane]) |*pt| status: { - const got = host_io.exitStatus(pt.pid, 100); - if (got != null) pt.pid = 0; - break :status got; - } else null; - s.closePty(e.pane); + if (s.gens[e.pane] == e.gen) eof: { if (e.failure) |err| { + s.closePty(e.pane); if (s.core.panes[e.pane]) |pane| { pane.body.mode = .normal; s.core.reportError(e.pane, "terminal reader", err); } - } else s.core.update(.{ .eof = .{ .pane = e.pane, .status = status } }); + break :eof; + } + const pt = if (s.ptys[e.pane]) |*pt| pt else { + s.core.update(.{ .eof = .{ .pane = e.pane } }); + break :eof; + }; + // 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) { + pt.cmd.eof = true; + s.core.update(.{ .eof = .{ .pane = e.pane } }); + s.takeExits(); + break :eof; + } + // Unwatched, a command's exit is read here, as its end. + const unwatched = if (s.core.panes[e.pane]) |pane| pane.command != null else false; + var status: ?u8 = null; + if (unwatched) { + status = host_io.exitStatus(pt.pid, 100); + if (status != null) pt.pid = 0; // reaped: no shell to retire + } + s.closePty(e.pane); + if (unwatched) s.core.update(.{ .exited = .{ .pane = e.pane, .status = status } }); + s.core.update(.{ .eof = .{ .pane = e.pane } }); } s.saw_event = true; }, @@ -4165,7 +4217,8 @@ fn spawnPane(ctx: ?*anyopaque, pane: u8, cwd: []const u8) void { if (s.ptys[pane] != null) return s.core.reportError(pane, "shell", error.WorkersBusy); 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); - const pt: Pty = .{ .fd = child.file.handle, .pid = child.pid, .serial = s.core.panes[pane].?.serial }; + const command = s.core.panes[pane].?.command != null; + const pt: Pty = .{ .fd = child.file.handle, .pid = child.pid, .serial = s.core.panes[pane].?.serial, .cmd = .{ .watched = command and host_io.watchExit(child.pid) } }; s.ptys[pane] = pt; var lbuf: [pardes.memory.limits.host_path_cap + 1]u8 = undefined; if (host_io.shellCwd(pt.pid, &lbuf)) |wd| s.core.setCwd(pane, wd); |
