aboutsummaryrefslogtreecommitdiffstats
path: root/config
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 /config
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 'config')
-rw-r--r--config/config.kdl38
1 files changed, 33 insertions, 5 deletions
diff --git a/config/config.kdl b/config/config.kdl
index db272a4..ef3c4f2 100644
--- a/config/config.kdl
+++ b/config/config.kdl
@@ -77,6 +77,18 @@ zoom_on_mark "*"
close_mark "x"
min_mark "_"
+// A pane whose program exits stays where it is, keeping everything it
+// printed, with a line in its own backlog saying why it ended and two buttons
+// in its frame: [re-run] to run the same command again in the same pane and
+// on top of the same scrollback, and [close] to let it go. `sl0ppty` closes
+// with the last pane, so a session no longer disappears the instant the shell
+// in it exits.
+//
+// false is the old behaviour: the pane is closed on the next paint, and with
+// the last pane the session. That suits a one-shot layout, and costs you the
+// last thing a failing command printed.
+keep_dead true
+
bell_indicator true
// What to draw. A short string rather than a fixed glyph, because the obvious
@@ -161,6 +173,7 @@ double_click_ms 400
// dragging the pane you have hold of
// drop_hover the one under the pointer, where it would land
// drop_target the others, all of them somewhere it could go
+// dead its program exited; waiting to be re-run or closed
// suspended laid out, never started (its label is its contents)
// scrolled looking at scrollback rather than at the present
// unfocused the weakest thing that can be true of a pane
@@ -174,13 +187,13 @@ double_click_ms 400
// Naming a state replaces its default outright — including with nothing, which
// is how you turn one off: states { drop_target { } }
//
-// There is deliberately no `dead`: a pane whose program exits is reaped before
-// the next frame is painted, so a shader for it could never draw.
-//
-// Only drop_hover and drop_target have defaults, shown here.
+// Only drop_hover, drop_target and dead have defaults, shown here. `dead` is
+// gentler than the drag greying on purpose: a dead pane is something you are
+// still meant to read.
states {
drop_hover { grayscale amount=200; dim amount=140 }
drop_target { grayscale amount=200; dim amount=140 }
+ dead { grayscale amount=200; dim amount=90 }
// dragging { }
// suspended { grayscale amount=180; dim amount=90 }
// scrolled { tint amount=30 color="#ffcc88" }
@@ -281,6 +294,18 @@ theme {
// ── the line along the bottom ───────────────────────────────────────────
status "#45454a" // session, tab, pane, and the pane index
status_state "#ffffff" // scrolled / alt screen / not started / exited
+ hint "#ffffff" // what the pointer is on, in the middle
+
+ // ── marks on a pane's frame ─────────────────────────────────────────────
+ bell "#ff5fd7" // a pane rang and has not been looked at
+ dead "#ff875f" // its program exited: the line in its backlog,
+ // and the words in its frame and the status line
+ pane_button "#45454a" // the frame's own close/zoom/minimise marks
+ pane_button_hover "#ff5fd7"
+
+ // ── the row of minimised panes ──────────────────────────────────────────
+ minbar "#45454a"
+ minbar_hover "#ff5fd7"
// ── the pane finder ─────────────────────────────────────────────────────
finder_fg "#ffffff"
@@ -307,7 +332,7 @@ theme {
// Actions: split-cols split-rows close-pane focus-left focus-right focus-up
// focus-down focus-next resize-left resize-right resize-up resize-down
// scroll-up scroll-down scroll-page-up scroll-page-down scroll-top
-// scroll-bottom zoom minimize new-tab next-tab prev-tab select-tab-1..9 finder detach quit
+// scroll-bottom zoom minimize rerun new-tab next-tab prev-tab select-tab-1..9 finder detach quit
// literal-prefix none
keys {
@@ -316,6 +341,9 @@ keys {
bind "\\" "split-cols"
bind "-" "split-rows"
bind "x" "close-pane"
+ // Run a dead pane's command again, in the same pane and on top of the
+ // same scrollback. Refused on a pane that is still running.
+ bind "r" "rerun"
bind "z" "zoom"
bind "m" "minimize"