diff options
| author | user <user@clank> | 2026-08-15 15:21:24 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-15 15:21:24 +0200 |
| commit | 8379c0e7e92eb31a351e5023a1364ec8e95b402c (patch) | |
| tree | 4e2628719f15123b93fe036fdf830e77d71c70d6 /src | |
| parent | sl0ppty: dim the panes you are not in, and a knob to say how much (diff) | |
| download | slosh-8379c0e7e92eb31a351e5023a1364ec8e95b402c.tar.gz slosh-8379c0e7e92eb31a351e5023a1364ec8e95b402c.tar.xz slosh-8379c0e7e92eb31a351e5023a1364ec8e95b402c.zip | |
sl0ppty: only panes that were given a command keep their corpse
Keeping every dead pane was too much. Split off a shell, do something in it,
type `exit` -- and you are left with a corpse to dismiss, which is fussiness
rather than information. You know what happened. You did it.
So the policy has a line now, and the line is the *command*, not who made the
pane. A pane told to run `npm run dev` is part of the session's shape: when it
dies you want the error it left and the button that runs it again. A pane that
is a shell is finished when you say so.
Not provenance, because a shell is a shell whether a layout file made it or
C-a \ did -- and that also means a session restored from dump-layout behaves
like the one it was dumped from, since the dump writes `command=` only for
panes that had one. The distinguishing mark turned out to need no new state:
pane_label() already holds the command a layout gave it and is empty for a
plain shell, so this is a policy over something already recorded.
`keep_dead` is now `commands` (default), `all` or `none`, and still reads
`true`/`false` as `all`/`none` -- that was the shipped spelling and a config
saying it should keep working rather than quietly changing behaviour.
Most of the diff is tests. The dead-pane suite had been written against the
session's own pane, which is a shell under the new rule, so it was testing the
half of the policy that now says "reap it". They run commanded panes from
layouts instead, and the pair of cases -- a shell you split off goes, a
commanded pane stays -- sits together in one place so the rule is legible in
the test file too.
One of those layouts wanted an escape sequence, which has to be written `\\033`
so KDL's unescaping hands `\033` to the shell. Written singly the shell prints
`033[38;2;...` and the test reads as "the colour stopped working".
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.c | 24 | ||||
| -rw-r--r-- | src/config.c | 18 | ||||
| -rw-r--r-- | src/config.h | 22 |
3 files changed, 49 insertions, 15 deletions
@@ -1444,18 +1444,30 @@ struct reap { app_t *a; node_t *dead; }; +/* Does this pane's corpse stay? The label is the command it was given, and is + * empty for a pane that is just a shell -- which is the whole distinction, and + * it happens to already be recorded. */ +static bool keep_corpse(const pane_t *p) { + switch (CFG.keep_dead) { + case KEEP_DEAD_ALL: return true; + case KEEP_DEAD_NONE: return false; + default: return pane_label(p)[0] != 0; + } +} + static void reap_cb(node_t *n, void *ud) { struct reap *r = ud; - if (!r->dead && !pane_alive(n->pane)) r->dead = n; + if (!r->dead && !pane_alive(n->pane) && !keep_corpse(n->pane)) r->dead = n; } -/* Closing a pane whose program exited is a *policy*, not bookkeeping — which - * is the whole of this feature. By default a dead pane stays: it keeps what - * it printed, says why it is over, and offers to run the command again. Under - * `keep_dead false` this reverts to the old behaviour and the pane goes. */ +/* Closing a pane whose program exited is a *policy*, not bookkeeping. + * + * A pane that was given a command keeps its corpse: the error is worth + * reading and [re-run] is worth having. A pane that is a shell goes when you + * type `exit`, because that is what typing `exit` means. `keep_dead` moves + * the line either way. */ void app_reap(app_t *a) { ensure_config(); - if (CFG.keep_dead) return; for (;;) { struct reap r = {a, NULL}; walk_all(a, reap_cb, &r); diff --git a/src/config.c b/src/config.c index 71dbb49..f4076b7 100644 --- a/src/config.c +++ b/src/config.c @@ -234,7 +234,7 @@ void config_defaults(config_t *c) { snprintf(c->newtab_mark, sizeof c->newtab_mark, "+"); c->bell_indicator = true; snprintf(c->bell_mark, sizeof c->bell_mark, "\u2022"); - c->keep_dead = true; + c->keep_dead = KEEP_DEAD_COMMANDS; /* Gentle: an unfocused pane is one you are still reading half the time. * At 60 white text lands on #c3c3c3, which reads as "not this one" without * reading as "not available". */ @@ -654,7 +654,21 @@ bool config_load(config_t *c, const char *path, char *err, size_t errcap) { if (mm) snprintf(c->min_mark, sizeof c->min_mark, "%s", mm); const char *nt = kdl_arg(kdl_child(root, "newtab_mark"), 0, NULL); if (nt) snprintf(c->newtab_mark, sizeof c->newtab_mark, "%s", nt); - c->keep_dead = kdl_arg_bool(kdl_child(root, "keep_dead"), 0, c->keep_dead); + { + /* `commands` (the default), `all`, or `none`. `true`/`false` are taken as + * `all`/`none`, because that is what they used to mean here and a config + * that still says so should keep working rather than silently changing + * behaviour. */ + const char *kd = kdl_arg(kdl_child(root, "keep_dead"), 0, NULL); + if (kd) { + if (!strcmp(kd, "all") || !strcmp(kd, "true")) c->keep_dead = KEEP_DEAD_ALL; + else if (!strcmp(kd, "none") || !strcmp(kd, "false")) + c->keep_dead = KEEP_DEAD_NONE; + else if (!strcmp(kd, "commands")) c->keep_dead = KEEP_DEAD_COMMANDS; + else if (err && !err[0]) + snprintf(err, errcap, "keep_dead: %s (want commands, all or none)", kd); + } + } { long v = kdl_arg_int(kdl_child(root, "dim_unfocused"), 0, c->dim_unfocused); c->dim_unfocused = (uint8_t)(v < 0 ? 0 : v > 255 ? 255 : v); diff --git a/src/config.h b/src/config.h index 6a93b45..882b8b0 100644 --- a/src/config.h +++ b/src/config.h @@ -119,13 +119,21 @@ typedef struct { * terminals while chrome here is booked as one — so the default is narrow * and anyone who knows their terminal can say otherwise. */ char bell_mark[16]; - /* A pane whose program exits stays, showing what it printed and offering to - * run it again, until it is closed. Off restores the old behaviour: the - * pane vanishes on the next paint, and with the last pane so does the - * session. That is a real choice — a one-shot layout wants it — but it is - * not the default, because a pane that disappears takes its own error - * message with it. */ - bool keep_dead; + /* Which dead panes stay, showing what they printed and offering to run + * again, and which just go. + * + * The line is whether the pane was given a *command*. A pane told to run + * `npm run dev` is part of the session's shape and its death is news: you + * want the error, and you want the button that runs it again. A pane that + * is a shell -- one you split off to do something in -- is finished when + * you type `exit`, and leaving its corpse on screen to be dismissed is + * exactly the fussiness a terminal should not have. + * + * Not *who made it*: a shell is a shell whether it came from a layout file + * or from C-a \. That also means a session restored from dump-layout + * behaves like the one it was dumped from, since the dump writes `command=` + * only for panes that had one. */ + enum { KEEP_DEAD_NONE, KEEP_DEAD_COMMANDS, KEEP_DEAD_ALL } keep_dead; uint16_t min_pane_cols, min_pane_rows; /* The smallest pane a split is allowed to *produce*. min_pane is the point * below which the layout gives up and collapses a pane; this is the point |
