aboutsummaryrefslogtreecommitdiffstats
path: root/src/config.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-14 21:32:40 +0200
committeruser <user@clank>2026-08-14 21:32:40 +0200
commitc5f4bb75403df3ca7e0bf6c30981c0cb4e54921e (patch)
tree44bddd3b1ba1ea0f4782b50d9c095c6b813f7b7b /src/config.c
parentsl0ppty: let a child's exit status survive long enough to be read (diff)
downloadslosh-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/config.c')
-rw-r--r--src/config.c19
1 files changed, 16 insertions, 3 deletions
diff --git a/src/config.c b/src/config.c
index 6ce7a7f..6b10d36 100644
--- a/src/config.c
+++ b/src/config.c
@@ -14,7 +14,7 @@ static const struct {
action_t action;
} ACTIONS[] = {
{"split-cols", ACT_SPLIT_COLS}, {"split-rows", ACT_SPLIT_ROWS},
- {"close-pane", ACT_CLOSE_PANE},
+ {"close-pane", ACT_CLOSE_PANE}, {"rerun", ACT_RERUN},
{"zoom", ACT_ZOOM},
{"minimize", ACT_MINIMIZE}, {"focus-left", ACT_FOCUS_LEFT},
{"focus-right", ACT_FOCUS_RIGHT}, {"focus-up", ACT_FOCUS_UP},
@@ -109,8 +109,8 @@ static bool parse_color(const char *text, color_t *out) {
}
static const char *const PSTATE_NAMES[PSTATE_COUNT] = {
- "dragging", "drop_hover", "drop_target", "suspended", "scrolled",
- "unfocused",
+ "dragging", "drop_hover", "drop_target", "dead", "suspended",
+ "scrolled", "unfocused",
};
const char *pane_state_name(pane_state_t s) {
@@ -179,6 +179,7 @@ void config_defaults(config_t *c) {
snprintf(c->min_mark, sizeof c->min_mark, "_");
c->bell_indicator = true;
snprintf(c->bell_mark, sizeof c->bell_mark, "\u2022");
+ c->keep_dead = true;
c->min_pane_cols = 24;
c->min_pane_rows = 6;
c->min_split_cols = 32;
@@ -211,6 +212,14 @@ void config_defaults(config_t *c) {
shader_make(&c->state_shaders[PSTATE_DROP_HOVER][1], "dim", (color_t){0},
140);
c->state_n[PSTATE_DROP_HOVER] = 2;
+ /* The other state with an opinion, and for the same reason the drag states
+ * have one: what is on those cells is no longer live, and colour is the
+ * only thing that says so without reading a word. Gentler than the drag
+ * greying, because a dead pane is something you still want to read. */
+ shader_make(&c->state_shaders[PSTATE_DEAD][0], "grayscale", (color_t){0},
+ 200);
+ shader_make(&c->state_shaders[PSTATE_DEAD][1], "dim", (color_t){0}, 90);
+ c->state_n[PSTATE_DEAD] = 2;
const color_t accent = rgb(0xff, 0x5f, 0xd7);
const color_t ink = rgb(0x14, 0x14, 0x18);
@@ -257,6 +266,7 @@ void config_defaults(config_t *c) {
c->finder_sel_bg = accent;
c->bell = accent;
+ c->dead = rgb(0xff, 0x87, 0x5f);
c->hint = bright;
c->minbar = dim;
c->minbar_hover = accent;
@@ -275,6 +285,7 @@ void config_defaults(config_t *c) {
bind_add(c, GHOSTTY_KEY_BACKSLASH, 0, ACT_SPLIT_COLS);
bind_add(c, GHOSTTY_KEY_MINUS, 0, ACT_SPLIT_ROWS);
bind_add(c, GHOSTTY_KEY_X, 0, ACT_CLOSE_PANE);
+ bind_add(c, GHOSTTY_KEY_R, 0, ACT_RERUN);
bind_add(c, GHOSTTY_KEY_Z, 0, ACT_ZOOM);
bind_add(c, GHOSTTY_KEY_M, 0, ACT_MINIMIZE);
bind_add(c, GHOSTTY_KEY_H, 0, ACT_FOCUS_LEFT);
@@ -388,6 +399,7 @@ bool config_load(config_t *c, const char *path, char *err, size_t errcap) {
if (cm) snprintf(c->close_mark, sizeof c->close_mark, "%s", cm);
const char *mm = kdl_arg(kdl_child(root, "min_mark"), 0, NULL);
if (mm) snprintf(c->min_mark, sizeof c->min_mark, "%s", mm);
+ c->keep_dead = kdl_arg_bool(kdl_child(root, "keep_dead"), 0, c->keep_dead);
c->bell_indicator =
kdl_arg_bool(kdl_child(root, "bell_indicator"), 0, c->bell_indicator);
const char *bm = kdl_arg(kdl_child(root, "bell_mark"), 0, NULL);
@@ -485,6 +497,7 @@ bool config_load(config_t *c, const char *path, char *err, size_t errcap) {
{"finder_sel_fg", &c->finder_sel_fg},
{"finder_sel_bg", &c->finder_sel_bg},
{"bell", &c->bell},
+ {"dead", &c->dead},
{"hint", &c->hint},
{"minbar", &c->minbar},
{"minbar_hover", &c->minbar_hover},