summaryrefslogtreecommitdiff
path: root/src/tty/tty.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/tty/tty.zig')
-rw-r--r--src/tty/tty.zig50
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;