diff options
| author | user <user@clank> | 2026-08-14 21:32:40 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-14 21:32:40 +0200 |
| commit | c5f4bb75403df3ca7e0bf6c30981c0cb4e54921e (patch) | |
| tree | 44bddd3b1ba1ea0f4782b50d9c095c6b813f7b7b /src/cmd.c | |
| parent | sl0ppty: let a child's exit status survive long enough to be read (diff) | |
| download | slosh-c5f4bb75403df3ca7e0bf6c30981c0cb4e54921e.tar.gz slosh-c5f4bb75403df3ca7e0bf6c30981c0cb4e54921e.tar.xz slosh-c5f4bb75403df3ca7e0bf6c30981c0cb4e54921e.zip | |
sl0ppty: a pane outlives the program that was in it
A pane whose program exited was reaped before the next paint. The pane went,
and with it everything the program had printed -- including, always, the one
line that would have explained why it stopped. Close the last one and the
session went too, so a mistyped command in a fresh session closed the
terminal you were typing in.
Now the pane stays. It keeps its contents, writes one line into its own
backlog saying how it ended -- `[process exited: status 3]`, `signal 9` --
says the same thing in its frame and in the status line, and offers the two
verbs that still mean anything:
╰ exited: status 3 ──────────────────────────────[re-run]─[close]─╯
`[re-run]` runs the same argv in the same cwd in the *same pane*: same id,
same node in the tree, same terminal. Nothing that referred to that pane has
to be told anything, and the run that ended stays above the new one in the
scrollback, which is what makes comparing two runs possible at all. C-a r
and {"cmd":"rerun"} do the same thing; a live pane is refused rather than
restarted, because "run it again" is not a request to kill something.
Those buttons are the OSC 5577 row, not a second mechanism. A dead pane's
own buttons are inert -- a click would write a report into a closed pty --
so the row is given over rather than added to. `close` is listed last, so it
is drawn rightmost and is the one that survives a narrow frame: being unable
to dismiss a dead pane is a trap, being unable to re-run one is an
inconvenience.
`dead` is a shader state now, which D13 said it could never be: it named a
frame that could not be drawn, and now there is one. Dead panes drain to
grey by default, so a wall of them reads at a glance. A collapsed header
says "exited" in words as well, because the shader pass does not reach one
and a flattened tab draws nothing else of a pane.
Two things about this are load-bearing rather than incidental:
- the pty fd is closed the moment EOF is seen. A pane that outlives its
program stays in the poll set, and an fd at EOF is readable forever: the
session would spin a core behind a pane that looks idle.
- the exit status is collected with a bounded wait, never a blocking one.
EOF means the program already closed its fds, so it is on its way out and
a few hundred microseconds wins that race. A blocking waitpid would hang
the whole session on one program that closes its terminal and keeps
running; losing the race costs the words "status 0" and nothing else.
`keep_dead false` restores the old behaviour for a one-shot layout that
wants it. `panes` reports alive/exit_code/exit_signal, so a script can find
the pane whose command failed and rerun it.
Diffstat (limited to 'src/cmd.c')
| -rw-r--r-- | src/cmd.c | 5 |
1 files changed, 5 insertions, 0 deletions
@@ -140,6 +140,11 @@ static char *cmd_json(app_t *a, screen_t *s, input_parser_t *in, return jerr("no such pane"); return jok_int("id", app_focused_pane_id(a)); } + if (strcmp(cmd, "rerun") == 0) { + if (!app_rerun_pane(a, (uint32_t)jv_geti(req, "id", 0))) + return jerr("cannot run it again"); + return jok_int("id", app_focused_pane_id(a)); + } if (strcmp(cmd, "close") == 0) { if (!app_close_pane(a, (uint32_t)jv_geti(req, "id", 0))) return jerr("no such pane"); |
