diff options
| author | user <user@clank> | 2026-09-22 15:38:08 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-22 15:38:08 +0200 |
| commit | 1cbfb4474ca31c61254be8d5f3ec046967bd1ff3 (patch) | |
| tree | 3e8184674e330ce24ebef23dd13594fa38f45fa2 | |
| parent | sidebar: room for the spinner, by default (tab_bar_padding 0 1 0 2) (diff) | |
| download | slosh-1cbfb4474ca31c61254be8d5f3ec046967bd1ff3.tar.gz slosh-1cbfb4474ca31c61254be8d5f3ec046967bd1ff3.tar.xz slosh-1cbfb4474ca31c61254be8d5f3ec046967bd1ff3.zip | |
theme: the busy spinner is its own colour (tab_status_spinner)
It was drawn in tab_status_busy, the colour of the words it sits beside, which
made one decision out of two. The words have to stay readable at a glance and
a mark does not: a theme may well want the glyph loud and the text calm -- or
the text in the accent and the mark a quiet dot -- and with one entry neither
is available without moving both.
Unset it follows tab_status_busy, derived after it and from whatever that
resolved to, named or derived, so nothing changes for anyone who does not ask
and no theme in contrib needs a line. `--dump-theme` writes both, like every
other colour the compositor draws.
| -rw-r--r-- | config/config.kdl | 4 | ||||
| -rw-r--r-- | docs/panes.md | 4 | ||||
| -rw-r--r-- | docs/scripting.md | 3 | ||||
| -rw-r--r-- | docs/slosh.5 | 7 | ||||
| -rw-r--r-- | include/app_internal.h | 1 | ||||
| -rw-r--r-- | include/config.h | 6 | ||||
| -rw-r--r-- | src/app_draw.c | 4 | ||||
| -rw-r--r-- | src/config.c | 6 | ||||
| -rw-r--r-- | tests/test_tab_bar.py | 60 | ||||
| -rw-r--r-- | tests/test_themes.py | 8 |
10 files changed, 95 insertions, 8 deletions
diff --git a/config/config.kdl b/config/config.kdl index 2a1fdac..b46f64d 100644 --- a/config/config.kdl +++ b/config/config.kdl @@ -282,7 +282,9 @@ bell_mark "[!]" // A pane can also say that what its status describes is still *happening* // (`busy` over OSC 5577, see the scripting chapter). In a sidebar that status -// then turns the theme's `tab_status_busy` colour and carries this spinner, +// then turns the theme's `tab_status_busy` colour and carries this spinner -- +// in `tab_status_spinner`, its own entry, so a theme can shout with the mark +// and keep the words calm; unset it follows the words -- // which is how "running" and "finished" tell themselves apart from another tab // without reading the words. // diff --git a/docs/panes.md b/docs/panes.md index d760b82..57e3014 100644 --- a/docs/panes.md +++ b/docs/panes.md @@ -100,7 +100,9 @@ be able to cost you a tab you can click. **A pane can say its status is still happening**, and the sidebar says so back: `busy` over OSC 5577 turns that row `tab_status_busy` and puts a spinner -(`busy_mark`) in the first column of the indent — so `make test` running and +(`busy_mark`, in `tab_status_spinner` — its own theme entry, because the words +have to stay readable and a mark does not, and unset it follows the words) in +the first column of the indent — so `make test` running and `make test` finished, which are the same words, stop looking the same from another tab. Nothing moves when it flips, since the mark takes a column the padding was already holding blank, and the rest of that padding is the space diff --git a/docs/scripting.md b/docs/scripting.md index 9971bc5..8fff400 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -115,7 +115,8 @@ keep it current and `clear` it when the work is done. of its own rather than a field of `status`, because a status is the *whole* payload after the verb — `status;a;b;c` is the text `a;b;c` — so anything added to that line would change what every sender already means. In a sidebar a busy -status gets the spinner (`busy_mark`) and `tab_status_busy`, which is how +status gets the spinner (`busy_mark`, coloured `tab_status_spinner`) and +`tab_status_busy`, which is how "working" and "done" tell themselves apart from another tab without reading the words. Anything but `0`, `false`, `no` or `off` turns it on; `clear` turns it off along with the text, and so does the program exiting — a status left behind diff --git a/docs/slosh.5 b/docs/slosh.5 index 62205cc..3c1a43a 100644 --- a/docs/slosh.5 +++ b/docs/slosh.5 @@ -224,7 +224,12 @@ is the spinner's frames separated by spaces; one frame is a static mark and costs nothing, several make it turn, and the session keeps a frame clock only while such a row is on screen. Empty draws no mark and leaves .B tab_status_busy -to say it. The mark sits in the first column +to say it. +The mark is drawn in +.BR tab_status_spinner , +which follows +.B tab_status_busy +unless a theme names it. The mark sits in the first column .B tab_bar_padding holds blank, so nothing moves when a program starts or stops working, and the rest of that padding is the space after it. diff --git a/include/app_internal.h b/include/app_internal.h index 1af9163..c699be8 100644 --- a/include/app_internal.h +++ b/include/app_internal.h @@ -413,6 +413,7 @@ extern config_t CFG; #define TAB_STATUS_FG (CFG.tab_status_fg) #define TAB_STATUS_BG (CFG.tab_status_bg) #define TAB_STATUS_BUSY (CFG.tab_status_busy) +#define TAB_STATUS_SPINNER (CFG.tab_status_spinner) #define TAB_STATUS_STRIPE (CFG.tab_status_stripe) #define STATUS_C (CFG.status) #define STATUS_STATE (CFG.status_state) diff --git a/include/config.h b/include/config.h index 40a5ace..e5a3ca5 100644 --- a/include/config.h +++ b/include/config.h @@ -547,6 +547,12 @@ typedef struct { * not be something you have to read the words to work out. Derived from the * theme, like tab_status_fg. */ color_t tab_status_busy; + /* The spinner beside such a row, which is its own entry because it is its + * own decision: the text has to stay readable, and a mark does not -- so a + * theme can shout with the glyph and keep the words calm, or the other way + * round. Unset it follows tab_status_busy, which is what it looked like + * when the two were one colour. */ + color_t tab_status_spinner; /* The other stripe. With wrapped statuses, where one pane's rows end and * the next one's begin is no longer obvious from the shape -- both are * just italic text down the same column -- so alternate panes sit on diff --git a/src/app_draw.c b/src/app_draw.c index 5676240..b3d8f07 100644 --- a/src/app_draw.c +++ b/src/app_draw.c @@ -2469,8 +2469,8 @@ void draw_tab_sidebar(app_t *a, screen_t *s) { * one column they stand shoulder to shoulder, which is the squashed * look and now something you ask for rather than something you get. */ if (marked && k == 0) - screen_text(s, cx, y, spin, hot ? TAB_HOVER : TAB_STATUS_BUSY, band, - 0); + screen_text(s, cx, y, spin, hot ? TAB_HOVER : TAB_STATUS_SPINNER, + band, 0); char act[24]; snprintf(act, sizeof act, "find:%u", ts.row[j].pane); hit_add(&s->hits, cx, y, cw, 1, act); diff --git a/src/config.c b/src/config.c index 012964e..60eb711 100644 --- a/src/config.c +++ b/src/config.c @@ -697,6 +697,7 @@ static const struct { {"tab_status_fg", offsetof(config_t, tab_status_fg)}, {"tab_status_bg", offsetof(config_t, tab_status_bg)}, {"tab_status_busy", offsetof(config_t, tab_status_busy)}, + {"tab_status_spinner", offsetof(config_t, tab_status_spinner)}, {"tab_status_stripe", offsetof(config_t, tab_status_stripe)}, {"status", offsetof(config_t, status)}, {"status_state", offsetof(config_t, status_state)}, @@ -1143,6 +1144,7 @@ void config_defaults(config_t *c) { * annotations that is still moving, and the spinner beside it is already * saying so -- the colour is what makes it findable without looking. */ c->tab_status_busy = accent; + c->tab_status_spinner = accent; /* Barely off the background: the stripe has to say "different pane", not * "selected". Only drawn when statuses can wrap -- see tab_bar_status_lines * -- so an unwrapped sidebar keeps its transparent rows. */ @@ -2967,6 +2969,10 @@ static bool load_into(config_t *c, const char *path, int depth, char *err, /* The busy one is the theme's own accent, which is what it uses for * "this one" everywhere else. */ if (!kdl_child(theme, "tab_status_busy")) c->tab_status_busy = c->tab_hover; + /* After it, and from it: the mark follows the words it marks unless a + * theme says otherwise, whether those words were named or derived. */ + if (!kdl_child(theme, "tab_status_spinner")) + c->tab_status_spinner = c->tab_status_busy; } /* After the theme, so the wash follows whatever scroll_bg the theme just * chose -- a config that wrote its own `scrolled` chain keeps it, whether diff --git a/tests/test_tab_bar.py b/tests/test_tab_bar.py index ad523ad..6a1a0a5 100644 --- a/tests/test_tab_bar.py +++ b/tests/test_tab_bar.py @@ -648,8 +648,64 @@ def test_the_busy_colour_is_the_themes_own(): _status(s, "make test") _busy(s) s.settle(30) - st = s.snapshot().style_at(CX + 1, Y0 + 1) + snap = s.snapshot() + st = snap.style_at(CX + 2, Y0 + 1) check("named, it wins", st["fg"] == "#ff00ff", str(st)) + check( + "and the mark follows the words it marks", + (snap.style_at(CX, Y0 + 1) or {}).get("fg") == "#ff00ff", + str(snap.style_at(CX, Y0 + 1)), + ) + + +def test_the_spinner_can_be_coloured_on_its_own(): + """Its own entry because it is its own decision: the words have to stay + readable and a mark does not, so a theme can shout with the glyph and keep + the text calm.""" + themed = _cfg( + f'tab_bar_side "left"\ntab_bar_width {W}\n' + 'theme { tab_status_busy "#8888ff"\n tab_status_spinner "#ffcc00" }\n' + ) + with Session(SH, cols=90, rows=20, config=themed) as s: + s.settle(30) + _status(s, "make test") + _busy(s) + s.settle(30) + snap = s.snapshot() + check( + "the mark takes its own colour", + (snap.style_at(CX, Y0 + 1) or {}).get("fg") == "#ffcc00", + str(snap.style_at(CX, Y0 + 1)), + ) + check( + "...and the words keep theirs", + (snap.style_at(CX + 2, Y0 + 1) or {}).get("fg") == "#8888ff", + str(snap.style_at(CX + 2, Y0 + 1)), + ) + + +def test_an_old_theme_gets_a_spinner_colour_anyway(): + """Neither colour is in any theme in contrib, so both are mixed from what a + theme does define -- the same bargain the other status colours make.""" + old = _cfg( + f'tab_bar_side "left"\ntab_bar_width {W}\ntheme {{ tab_hover "#00ff88" }}\n' + ) + with Session(SH, cols=90, rows=20, config=old) as s: + s.settle(30) + _status(s, "make test") + _busy(s) + s.settle(30) + snap = s.snapshot() + check( + "the row is the theme's accent", + (snap.style_at(CX + 2, Y0 + 1) or {}).get("fg") == "#00ff88", + str(snap.style_at(CX + 2, Y0 + 1)), + ) + check( + "...and so is the mark", + (snap.style_at(CX, Y0 + 1) or {}).get("fg") == "#00ff88", + str(snap.style_at(CX, Y0 + 1)), + ) def test_status_rows_take_their_own_theme_colours(): @@ -1272,6 +1328,8 @@ if __name__ == "__main__": test_a_spinner_costs_a_frame_clock_and_a_mark_does_not() test_no_busy_mark_leaves_the_colour_to_say_it() test_the_busy_colour_is_the_themes_own() + test_the_spinner_can_be_coloured_on_its_own() + test_an_old_theme_gets_a_spinner_colour_anyway() test_status_rows_take_their_own_theme_colours() test_an_old_theme_gets_coherent_status_colours_anyway() test_a_monochrome_theme_stays_monochrome() diff --git a/tests/test_themes.py b/tests/test_themes.py index d9c67b0..e9f5495 100644 --- a/tests/test_themes.py +++ b/tests/test_themes.py @@ -26,7 +26,13 @@ THEMES = sorted(glob.glob(os.path.join(THEMEDIR, "*.kdl"))) # from colours the theme does define (so a theme gets them in its own family # without a line), and the opt-in ones are unset on purpose, because a band # behind a row takes a translucent terminal's own background away. -DERIVED = {"tab_status_fg", "tab_status_stripe", "tab_status_busy", "tab_idle_bg"} +DERIVED = { + "tab_status_fg", + "tab_status_stripe", + "tab_status_busy", + "tab_status_spinner", + "tab_idle_bg", +} OPT_IN = {"tab_status_bg", "attach_bg"} # The scrollback search tints: one theme in contrib names them and the rest # take the compiled-in pair, which reads the same on every palette here. |
