aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-16 15:05:02 +0200
committeruser <user@clank>2026-08-16 15:05:02 +0200
commitab8165a9717b4dd2b6dc750f504d4f67f6d9fdc6 (patch)
treeaca329fd1453a0739d714bcd190cd155780dd0ee /src
parentsl0ppty: a prompt with a memory (diff)
downloadslosh-ab8165a9717b4dd2b6dc750f504d4f67f6d9fdc6.tar.gz
slosh-ab8165a9717b4dd2b6dc750f504d4f67f6d9fdc6.tar.xz
slosh-ab8165a9717b4dd2b6dc750f504d4f67f6d9fdc6.zip
sl0ppty: three ways back from a painted pane
printf '\033]5577;1;shader;\033\\' # from the program sl0ppty cmd '{"cmd":"clear-shaders","id":0}' # from outside it bind "z" "clear-shaders" # from the keyboard `app_clear_pane_shaders` empties both of a pane's chains and says whether there was anything there, which is what lets a key answer "shaders cleared" or "no shaders on this pane" instead of maybe having done something. In-band it is not a new verb: naming no rect means all of them. `shader;` sits under `shader;chrome;` and `shader;content;` as the degenerate case of "which rect", so a program puts a pane back in one exchange and cannot leave it half done. `contrib/shader-repl`'s `:both` now uses that rather than two. **None of the three is gated on `in_band_shaders`.** A chain outlives the setting that allowed it -- paint a pane, then turn the setting off, and the paint is still there, because a chain belongs to the pane and not to the file (there is a test that reloads with it off and clears anyway). A way out that the setting can take away is not a way out. The operator path matters for the same reason: the program that made a pane unreadable is not always in a state to put it back. They clear what the *pane* set. The config's chains and the session's own dimming are not this pane's doing and are derived every frame anyway. The action is bound to nothing, like `scroll-up`: it is in the palette, which is what the palette is for, and the docs say to bind it if you leave the setting on. Which is where a second bug turned up -- the palette truncates a label at 26 characters, so "undo this pane's own shaders" read as "undo this pane's own shade" on screen. Renamed to fit, and `tests/test_palette.py` now checks every label against the column, since nothing failed for that: it just looked like a typo in front of someone.
Diffstat (limited to 'src')
-rw-r--r--src/app.c45
-rw-r--r--src/app.h10
-rw-r--r--src/cmd.c8
-rw-r--r--src/config.c4
-rw-r--r--src/config.h1
5 files changed, 64 insertions, 4 deletions
diff --git a/src/app.c b/src/app.c
index 3c500a3..01a5536 100644
--- a/src/app.c
+++ b/src/app.c
@@ -432,6 +432,20 @@ static void on_pane_shader(app_t *a, pane_t *p, const char *payload) {
bool content = wlen == 7 && memcmp(payload, "content", 7) == 0;
char reply[256];
+ struct bypane b = {p, NULL};
+ walk_all(a, bypane_cb, &b);
+ if (!b.found) return;
+
+ /* Naming no rect means all of them: `shader;` puts the pane back in one
+ * exchange. The degenerate case of "which rect" rather than a verb of its own,
+ * because `shader;chrome;` and `shader;content;` already mean "this rect, this
+ * chain" and "" is the honest name for both of them. */
+ if (!wlen) {
+ app_clear_pane_shaders(a, b.found->id);
+ snprintf(reply, sizeof reply, "\033]5577;1;shader-reply;ok\033\\");
+ pane_write(p, reply, strlen(reply));
+ return;
+ }
if (!chrome && !content) {
snprintf(reply, sizeof reply,
"\033]5577;1;shader-reply;error;where must be content or chrome\033\\");
@@ -439,10 +453,6 @@ static void on_pane_shader(app_t *a, pane_t *p, const char *payload) {
return;
}
- struct bypane b = {p, NULL};
- walk_all(a, bypane_cb, &b);
- if (!b.found) return;
-
char err[192] = {0};
const char *text = semi ? semi + 1 : "";
bool ok = app_set_pane_shaders(a, b.found->id, chrome, text, err, sizeof err);
@@ -2885,6 +2895,26 @@ static void draw_collapsed(app_t *a, screen_t *s, node_t *n) {
* Refused, with a reason, when `in_band_shaders` is off -- a program restyling
* itself by accident is exactly what that decision was about, and the answer is
* a line of config rather than a rule nobody can lift. */
+/* Everything this pane painted on itself, gone: both chains at once.
+ *
+ * Not gated on `in_band_shaders`, unlike setting one. A chain can outlive the
+ * consent that allowed it -- set it, then turn the setting off, and the paint is
+ * still there -- so the way out must not be the thing the setting controls. It is
+ * also the operator's answer to a pane that has made itself unreadable, and an
+ * answer you have to edit a config to reach is not one.
+ *
+ * Says nothing about the config's own chains or the session's policy passes:
+ * those are not this pane's doing and are derived every frame anyway. */
+bool app_clear_pane_shaders(app_t *a, uint32_t pane_id) {
+ node_t *n = pane_by_id(a, pane_id ? pane_id : app_focused_pane_id(a));
+ if (!n) return false;
+ bool had = n->content_chain.n || n->chrome_chain.n;
+ chain_clear(&n->content_chain);
+ chain_clear(&n->chrome_chain);
+ if (had) pane_touch(n->pane);
+ return had;
+}
+
bool app_set_pane_shaders(app_t *a, uint32_t pane_id, bool chrome,
const char *text, char *err, size_t errcap) {
if (err && errcap) err[0] = 0;
@@ -4789,6 +4819,13 @@ static bool run_action(app_t *a, action_t act) {
case ACT_RESIZE_RIGHT: resize_focus(a, 1, 0); return true;
case ACT_RESIZE_UP: resize_focus(a, 0, -1); return true;
case ACT_RESIZE_DOWN: resize_focus(a, 0, 1); return true;
+ case ACT_CLEAR_SHADERS:
+ /* Says which of the two happened. "Nothing to undo" and "undone" look
+ * identical on a pane that was never painted, and a key that might have
+ * done nothing is a key you press again. */
+ app_toast(a, app_clear_pane_shaders(a, 0) ? "shaders cleared"
+ : "no shaders on this pane");
+ return true;
case ACT_EQUALIZE:
if (!app_equalize_splits(a)) app_toast(a, "nothing to even out");
return true;
diff --git a/src/app.h b/src/app.h
index 6a383c9..226aad2 100644
--- a/src/app.h
+++ b/src/app.h
@@ -148,6 +148,16 @@ uint32_t app_current_tab_id(app_t *a);
bool app_set_pane_shaders(app_t *a, uint32_t pane_id, bool chrome,
const char *text, char *err, size_t errcap);
+/* Undo both of one pane's own chains. 0 means the focused pane. False when there
+ * was nothing to undo, so a caller can say so rather than flash a frame nobody
+ * changed.
+ *
+ * Deliberately not gated on `in_band_shaders`: a chain can outlive the consent
+ * that allowed it, and the way out must not be the thing the setting controls.
+ * Leaves the config's chains and the session's policy passes alone -- neither is
+ * this pane's doing. */
+bool app_clear_pane_shaders(app_t *a, uint32_t pane_id);
+
/* The session written back out as a layout file: tabs, splits, proportions,
* directories and commands. The inverse of app_apply_layout_text(), and what
* makes "quit, rebuild, come back to the same screen" possible. Caller frees.
diff --git a/src/cmd.c b/src/cmd.c
index 5c3792a..556b32b 100644
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -168,6 +168,14 @@ static char *cmd_json(app_t *a, screen_t *s, input_parser_t *in,
return jerr("no such pane");
return jok_int(NULL, 0);
}
+ /* The way in from outside the pane, for when the program that painted it will
+ * not or cannot put it back. `ok` either way, with `cleared` saying whether
+ * there was anything there: a script asking for a clean pane got one, and that
+ * is not an error. */
+ if (strcmp(cmd, "clear-shaders") == 0) {
+ bool had = app_clear_pane_shaders(a, (uint32_t)jv_geti(req, "id", 0));
+ return jok_int("cleared", had ? 1 : 0);
+ }
if (strcmp(cmd, "new-tab") == 0) {
uint32_t id = app_new_tab(a, jv_gets(req, "name", ""));
if (!id) return jerr("cannot create tab");
diff --git a/src/config.c b/src/config.c
index 70667ba..24837cb 100644
--- a/src/config.c
+++ b/src/config.c
@@ -24,6 +24,7 @@ static const struct {
{"resize-left", ACT_RESIZE_LEFT}, {"resize-right", ACT_RESIZE_RIGHT},
{"resize-up", ACT_RESIZE_UP}, {"resize-down", ACT_RESIZE_DOWN},
{"equalize", ACT_EQUALIZE},
+ {"clear-shaders", ACT_CLEAR_SHADERS},
{"rotate-layout", ACT_ROTATE_LAYOUT},
{"scroll-up", ACT_SCROLL_UP}, {"scroll-down", ACT_SCROLL_DOWN},
{"scroll-page-up", ACT_SCROLL_PAGE_UP},
@@ -829,6 +830,9 @@ static const struct {
/* Short enough to fit the palette's label column, which truncates at 26 —
* "give every pane an even share" read as "give every pane an even sh". */
{ACT_EQUALIZE, "size", "even out every split"},
+ /* Under `panes`, not `size`: it undoes what the program in the pane did to
+ * the pane, which is a fact about that pane and not about the layout. */
+ {ACT_CLEAR_SHADERS, "panes", "clear this pane's shaders"},
{ACT_ROTATE_LAYOUT, "size", "turn the layout a quarter"},
{ACT_NEW_TAB, "tabs", "new tab"},
diff --git a/src/config.h b/src/config.h
index 815b58a..17c759c 100644
--- a/src/config.h
+++ b/src/config.h
@@ -70,6 +70,7 @@ typedef enum {
ACT_RESIZE_UP,
ACT_RESIZE_DOWN,
ACT_EQUALIZE,
+ ACT_CLEAR_SHADERS,
ACT_DETACH,
ACT_QUIT,
ACT_HELP,