aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
authorPeter Geissler <peter@haxx.in>2026-08-28 14:41:42 +0200
committerPeter Geissler <peter@haxx.in>2026-08-28 14:41:42 +0200
commit2fbd36c1cef6ea6c9fcf9c36afb5209089288257 (patch)
treec95a97a4277306eddc7daeb67dddac6daa1e943d /include
parentwindows build: re-stamp version.h when the version changes (diff)
downloadslosh-2fbd36c1cef6ea6c9fcf9c36afb5209089288257.tar.gz
slosh-2fbd36c1cef6ea6c9fcf9c36afb5209089288257.tar.xz
slosh-2fbd36c1cef6ea6c9fcf9c36afb5209089288257.zip
ctrl_d_exits: Ctrl-D at a prompt, where the tty cannot do it
Windows has no VEOF. The console driver does not know the character, so ^D arrives at cmd.exe and PowerShell as an ordinary keystroke and they ignore it -- it even echoes, which is how you find out: the line reads `echo hello^D`. The one key that closes a pane on every other platform does nothing at all, and closing a pane becomes a word you have to type. That you can type `exit` instead is the tell: the intent was never ambiguous, only unspoken. So slosh speaks it. What goes to the pane is the word, not a signal and not a close from outside -- the shell runs its own cleanup, returns its own status, and keep_dead decides what is left behind, all three of which reaching past it would skip. It is also what the user would have typed, so there is nothing new to explain. The knob is what makes it honest, because a wrong guess costs a pane. It fires only with the shell itself in the foreground (a program that is running may want the EOF for itself -- `cat` reading to end is the whole job), only on the primary screen (in an editor or a pager ^D is half a page down, and the alternate screen is the pane stating that rather than us matching process names), and only on an empty line (POSIX sends EOF there; what it does not do is discard what you typed). The line is an estimate. The shell's editor is on the far side of a pty, so pane_send_key counts what it forwarded since the last Enter. Editing it does not model -- a recalled history entry, ^U, completion -- leaves the count too high, which forgets to exit. That is the direction to be wrong in; the other one throws away a half-typed command. Default true on Windows, false on POSIX, where the line discipline already does this and does it properly: ^D there is also EOF for `cat` and every REPL, none of which is ours to intercept. A knob rather than an #ifdef so the guards are the same code everywhere and can be tested where the test suite runs -- test_ctrl_d.py turns it on and drives all four decisions, plus the case that does exit. Telling trapped from forwarded needs a program that reports what it received: on POSIX a forwarded ^D also ends an idle shell, and looking for the word `exit` cannot separate them either, because bash prints `exit` itself on EOF. Verified on the Windows box as well, where it is the default: ^D at an idle cmd.exe prompt takes the pane (and with it the session, having no panes left), `more` in the foreground keeps its own EOF, and a half-typed line survives with the line still on screen.
Diffstat (limited to 'include')
-rw-r--r--include/config.h24
-rw-r--r--include/slosh.h5
2 files changed, 29 insertions, 0 deletions
diff --git a/include/config.h b/include/config.h
index 00add4e..b7d5023 100644
--- a/include/config.h
+++ b/include/config.h
@@ -306,6 +306,30 @@ typedef struct {
uint16_t status_pad;
bool focus_follows_mouse;
+ /* Whether Ctrl-D at an idle shell prompt exits that shell, the way the tty
+ * line discipline would do it on POSIX.
+ *
+ * Windows has no VEOF: the console driver does not know the character, so
+ * cmd.exe and PowerShell simply ignore it and the one keystroke that closes
+ * a pane everywhere else does nothing. Typing `exit` works, which is the
+ * tell -- the intent was never ambiguous, only unspoken. So slosh speaks it,
+ * and that is exactly what it sends: the pane exits through the same door as
+ * a typed `exit`, with the shell's own cleanup and exit status, and
+ * `keep_dead` decides what is left behind. Closing the pane from outside
+ * would skip all three.
+ *
+ * Off on POSIX, where the line discipline already does this properly and
+ * an emulation could only get in the way -- ^D there is also EOF for `cat`
+ * and every REPL, and none of that is ours to intercept. On for anyone who
+ * wants the same key to mean the same thing on both.
+ *
+ * Narrow on purpose, because a wrong guess costs a pane. It fires only with
+ * the shell itself in the foreground (no child process to be sending EOF
+ * to), on the primary screen (in an editor or a pager ^D is half a page
+ * down), and on an empty line -- where POSIX would send EOF rather than
+ * discard what you typed. */
+ bool ctrl_d_exits;
+
/* Whether the program running in a pane may set that pane's shader chains
* over OSC 5577. Off, because a program restyling the session it happens to
* be running in is a hazard and not a feature (D13) -- and on for anyone
diff --git a/include/slosh.h b/include/slosh.h
index 3e293b3..e1ab08b 100644
--- a/include/slosh.h
+++ b/include/slosh.h
@@ -248,6 +248,11 @@ void pane_scroll_edge(pane_t *p, bool top);
bool pane_scrolled(const pane_t *p);
void pane_scroll_pos(const pane_t *p, uint32_t *above, uint32_t *total);
bool pane_alt_screen(const pane_t *p);
+/* Whether the line being typed looks empty, counted from the keys forwarded
+ * since the last Enter. An estimate: the shell's line editor is on the far
+ * side of the pty and cannot be asked. Only ctrl_d_exits reads it, and only
+ * to refuse -- see the field in pane.c for which way it errs. */
+bool pane_line_empty(const pane_t *p);
bool pane_wants_mouse(const pane_t *p);
const char *pane_title(const pane_t *p);
/* A BEL arrived and the pane has not been looked at since. */