aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-15 16:13:46 +0200
committeruser <user@clank>2026-08-15 16:13:46 +0200
commitee6288a7eadeb43c83a5ab78f6c65cffa4748a53 (patch)
tree8fc137687f47a7f20f0a2766bca17cd53e0400dc /src/app.c
parentmake coverage: a number, cheaply (diff)
downloadslosh-ee6288a7eadeb43c83a5ab78f6c65cffa4748a53.tar.gz
slosh-ee6288a7eadeb43c83a5ab78f6c65cffa4748a53.tar.xz
slosh-ee6288a7eadeb43c83a5ab78f6c65cffa4748a53.zip
sl0ppty: C-a e opens the config, in a pane, in your editor
The config watcher's whole point is the loop -- edit, save, watch the session change under you, edit again -- and the loop had a hole in the middle of it: you had to leave the session, or remember where the file lives, to start. So: one keystroke opens `$EDITOR <config>` in a new pane, down rather than across because a config file is lines and half the height beats half the width for reading them. `editor` in the config beats $EDITOR, and vi is the fallback, being the one editor a POSIX system must have -- a wrong guess you can see and change beats a pane that opens empty. The interesting part is what kind of pane it is. Panes given a command keep their corpse (D14), which is right for a dev server and wrong for this: quit the editor and you would be left dismissing a dead pane you opened thirty seconds ago. Rather than gaming that policy by giving the pane no command -- which would be a lie in the one field dump-layout and re-run read -- panes can now be *ephemeral*: a task rather than part of the session's shape. An ephemeral pane keeps no corpse and is not written into a dumped layout, so a restart does not reopen somebody's editor on a file they had finished with. That concept is the reusable half of this commit; the keybinding is the part you notice.
Diffstat (limited to 'src/app.c')
-rw-r--r--src/app.c67
1 files changed, 62 insertions, 5 deletions
diff --git a/src/app.c b/src/app.c
index a7d50be..c3811d5 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1294,10 +1294,22 @@ static void replace_child(node_t *parent, node_t *old, node_t *new_) {
}
}
+/* The pane a split makes. NULL argv means "whatever this session runs", which
+ * is the ordinary case; a command makes it a task pane instead. */
+static node_t *split_node_with(app_t *a, node_t *leaf, split_dir_t dir,
+ bool before, const char *const *argv,
+ const char *label);
+
static void split_node(app_t *a, node_t *leaf, split_dir_t dir, bool before) {
- if (!leaf) return;
- node_t *fresh = leaf_new(a);
- if (!fresh) return;
+ split_node_with(a, leaf, dir, before, NULL, NULL);
+}
+
+static node_t *split_node_with(app_t *a, node_t *leaf, split_dir_t dir,
+ bool before, const char *const *argv,
+ const char *label) {
+ if (!leaf) return NULL;
+ node_t *fresh = argv ? leaf_new_ex(a, argv, NULL, false, label) : leaf_new(a);
+ if (!fresh) return NULL;
/* Growing an existing split in the same direction keeps the tree flat, so
* three vertical splits are three equal columns rather than 1/2 + 1/4 + 1/4. */
@@ -1332,6 +1344,7 @@ static void split_node(app_t *a, node_t *leaf, split_dir_t dir, bool before) {
cur(a)->focus = fresh;
layout(a);
+ return fresh;
}
/* Would splitting this pane produce two panes worth having?
@@ -1448,6 +1461,10 @@ struct reap {
* 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) {
+ /* A pane opened to do one thing is done when that thing ends, whatever the
+ * policy says about commands: you asked for an editor, not for a record of
+ * having had one. */
+ if (pane_ephemeral(p)) return false;
switch (CFG.keep_dead) {
case KEEP_DEAD_ALL: return true;
case KEEP_DEAD_NONE: return false;
@@ -1690,6 +1707,40 @@ bool app_close_pane(app_t *a, uint32_t id) {
* the run that ended stays above the new one in its scrollback. A suspended
* pane is started rather than restarted, because "run the thing this pane is
* for" is the same request either way. */
+/* Open the config in $EDITOR, in a pane of its own.
+ *
+ * The whole point is the loop: edit, save, watch the session change under you,
+ * edit again. Anything that makes you leave the session to do it -- another
+ * terminal, or remembering where the file lives -- is friction in the middle
+ * of the one workflow the config watcher exists for.
+ *
+ * The pane is ephemeral: quit the editor and it is gone. You asked for an
+ * editor, not for a record of having had one. */
+bool app_edit_config(app_t *a) {
+ node_t *n = a->ntabs ? cur(a)->focus : NULL;
+ if (!n) return false;
+
+ const char *editor = CFG.editor && *CFG.editor ? CFG.editor : getenv("EDITOR");
+ /* vi is the one editor a POSIX system is required to have. Better a wrong
+ * guess you can see and change than a pane that opens empty. */
+ if (!editor || !*editor) editor = "vi";
+
+ char cmd[1024];
+ snprintf(cmd, sizeof cmd, "%s '%s'", editor, config_default_path());
+ const char *argv[] = {"/bin/sh", "-c", cmd, NULL};
+
+ /* Down rather than across: a config file is lines, and half the width of a
+ * pane is a worse place to read them than half the height. */
+ if (!split_fits(n, SPLIT_ROWS)) {
+ app_toast(a, "no room to split down");
+ return false;
+ }
+ node_t *fresh = split_node_with(a, n, SPLIT_ROWS, false, argv, cmd);
+ if (!fresh) return false;
+ pane_set_ephemeral(fresh->pane, true);
+ return true;
+}
+
bool app_rerun_pane(app_t *a, uint32_t id) {
node_t *n = id ? pane_by_id(a, id) : (a->ntabs ? cur(a)->focus : NULL);
if (!n) return false;
@@ -4013,6 +4064,9 @@ static bool prefix_command(app_t *a, const input_event_t *ev) {
a->query[0] = 0;
a->sel = 0;
return true;
+ case ACT_EDIT_CONFIG:
+ app_edit_config(a);
+ return true;
case ACT_HELP:
a->help = !a->help;
return true;
@@ -4336,8 +4390,11 @@ static void dump_node(app_t *a, node_t *n, strbuf_t *b, int depth) {
char cwdbuf[4096];
sb_quoted(b, "cwd", live_cwd(n->pane, cwdbuf, sizeof cwdbuf));
/* The label is the command a layout gave it; a pane running the session's
- * default shell has none, and gets none back. */
- sb_quoted(b, "command", pane_label(n->pane));
+ * default shell has none, and gets none back. An ephemeral pane is a task
+ * that happened to be open when the dump was taken — restoring it would
+ * reopen somebody's editor on a file they finished with. */
+ if (!pane_ephemeral(n->pane))
+ sb_quoted(b, "command", pane_label(n->pane));
sb_quoted(b, "purpose", n->purpose);
if (pane_suspended(n->pane)) sb_add(b, " suspended=true");
if (n == cur(a)->focus) sb_add(b, " focus=true");