aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-10 16:20:47 +0200
committeruser <user@clank>2026-09-10 16:20:47 +0200
commita573c2b72486f88853bfc59ac0249578bd3aa548 (patch)
tree9b2298090e175eaa9775a1810e3ea6c72649a294
parentsidebar: tab_bar_pad back to 0, now that the frame is the separation (diff)
downloadslosh-a573c2b72486f88853bfc59ac0249578bd3aa548.tar.gz
slosh-a573c2b72486f88853bfc59ac0249578bd3aa548.tar.xz
slosh-a573c2b72486f88853bfc59ac0249578bd3aa548.zip
sidebar: the new-tab button says what it is, given a row to say it on
The mark went bare because in the strip it sits at the end of a row of tabs, where the company it keeps says what it does and columns are scarce. A sidebar row is neither: it is a row of its own with columns to spare, and a lone glyph on it reads as decoration -- the hint under the pointer cannot help, since you have to suspect a thing is a button before you hover it. So with the room it reads '+ new tab', in the word hint_for already gives this action rather than a second name for the same thing, and falls back to the bare mark when the sidebar is too narrow. The strip is untouched. The row fills and hits like a tab's, so hover and the drop-target fill cover the row rather than the word on it.
-rw-r--r--docs/panes.md3
-rw-r--r--src/app_draw.c53
-rw-r--r--tests/test_tab_bar.py45
3 files changed, 84 insertions, 17 deletions
diff --git a/docs/panes.md b/docs/panes.md
index 329e017..ab9bd08 100644
--- a/docs/panes.md
+++ b/docs/panes.md
@@ -62,7 +62,8 @@ tab_bar_chrome true // frame it like a pane
A **sidebar lists one tab per row** down the chosen edge, and the panes trade
the strip's row for its columns. Everything the strip does travels with it:
click to switch, double-click to rename in place, drag a pane onto a tab or
-onto the `+`, drag tabs to reorder. The pane count and the prefix badge move
+onto the `+` — which, given a row of its own and the columns to spare, spells
+itself out as `+ new tab`. The pane count and the prefix badge move
to the sidebar's bottom rows; the [status line](config.md) along the bottom
keeps the full width. A terminal too narrow to give up the columns falls back
to the top row until it grows.
diff --git a/src/app_draw.c b/src/app_draw.c
index 7614582..54b8950 100644
--- a/src/app_draw.c
+++ b/src/app_draw.c
@@ -1903,29 +1903,50 @@ static uint16_t draw_tab_cell(app_t *a, screen_t *s, size_t i, uint16_t x,
return hit_w;
}
-/* A bare mark, spaced like the frame's own buttons rather than spelled out.
- * It used to read "+tab", because a pane frame carried a "+" for splitting
- * and two verbs that look identical is a UI bug the fork shipped. That "+"
- * went when the border became the button, so the collision it was avoiding
- * no longer exists and the word was left explaining itself to nobody.
+/* In the strip, a bare mark, spaced like the frame's own buttons rather than
+ * spelled out. It used to read "+tab", because a pane frame carried a "+" for
+ * splitting and two verbs that look identical is a UI bug the fork shipped.
+ * That "+" went when the border became the button, so the collision it was
+ * avoiding no longer exists and the word was left explaining itself to
+ * nobody: in the strip the mark sits at the end of a row of tabs, where what
+ * it does is plain from the company it keeps.
*
- * Padded to three cells for the same reason the frame's buttons are: a
- * one-cell target is a thing you miss with a mouse. What it does is said by
- * the hint under the pointer, which is where every other one-character
- * affordance here says it. */
+ * A sidebar row is not that. It is a row of its own with columns to spare,
+ * and a lone glyph on it reads as decoration rather than as a button -- the
+ * hint under the pointer cannot rescue it either, because you have to suspect
+ * a thing is a button before you hover it. So given the room it says the
+ * word, and the word is the one `hint_for` already gives this action rather
+ * than a second name for the same thing.
+ *
+ * `max_w` is the row to fill; 0 is the strip's natural width. Three cells at
+ * minimum for the same reason the frame's buttons are: a one-cell target is a
+ * thing you miss with a mouse. */
static void draw_newtab_button(app_t *a, screen_t *s, uint16_t x, uint16_t y,
- bool dragging_pane) {
- char btn[24];
- snprintf(btn, sizeof btn, " %s ", CFG.newtab_mark);
+ uint16_t max_w, bool dragging_pane) {
+ char btn[64];
+ if (max_w && (uint16_t)(cells(CFG.newtab_mark) + 10) <= max_w)
+ snprintf(btn, sizeof btn, " %s new tab ", CFG.newtab_mark);
+ else
+ snprintf(btn, sizeof btn, " %s ", CFG.newtab_mark);
+ /* Padded out on a sidebar row for the same reason a tab's label is: the
+ * hover and the drop fill are the row, not the word sitting on it. */
+ if (max_w) {
+ size_t len = strlen(btn);
+ while (cells(btn) < max_w && len + 1 < sizeof btn) {
+ btn[len++] = ' ';
+ btn[len] = 0;
+ }
+ }
uint16_t w = screen_text(s, x, y, btn, TAB_IDLE, NO_COLOR, 0);
- if (ptr_on(a, x, y, w, 1))
+ uint16_t hit_w = max_w ? max_w : w;
+ if (ptr_on(a, x, y, hit_w, 1))
screen_text(s, x, y, btn, TAB_HOVER, NO_COLOR, ATTR_BOLD);
/* The button that makes a tab is also a place to drop a pane into one. */
if (dragging_pane)
screen_text(s, x, y, btn,
a->drag.new_tab_target ? TAB_ACTIVE_HOVER_FG : DROP_C,
a->drag.new_tab_target ? DROP_C : NO_COLOR, ATTR_BOLD);
- hit_add(&s->hits, x, y, w, 1, "newtab");
+ hit_add(&s->hits, x, y, hit_w, 1, "newtab");
}
void draw_tab_strip(app_t *a, screen_t *s) {
@@ -1968,7 +1989,7 @@ void draw_tab_strip(app_t *a, screen_t *s) {
{
uint16_t bw = (uint16_t)(cells(CFG.newtab_mark) + 2);
if (bw > 2 && x + bw <= right)
- draw_newtab_button(a, s, x, y, dragging_pane);
+ draw_newtab_button(a, s, x, y, 0, dragging_pane);
}
}
@@ -2126,7 +2147,7 @@ void draw_tab_sidebar(app_t *a, screen_t *s) {
}
if (cells(CFG.newtab_mark) && y < limit)
- draw_newtab_button(a, s, cx, y, dragging_pane);
+ draw_newtab_button(a, s, cx, y, cw, dragging_pane);
}
/* The space between two of a split's children, or false if they are flush.
diff --git a/tests/test_tab_bar.py b/tests/test_tab_bar.py
index eab817a..ce3ad9b 100644
--- a/tests/test_tab_bar.py
+++ b/tests/test_tab_bar.py
@@ -303,6 +303,48 @@ def test_chrome_false_gives_the_columns_back():
)
+def test_the_sidebar_button_says_what_it_does():
+ with Session(SH, cols=90, rows=20, config=LEFT) as s:
+ s.settle(40)
+ snap = s.snapshot()
+ row = _row(snap, Y0 + 1)
+ check("a row of its own spells the verb out", "new tab" in row, repr(row))
+ check(
+ "and the whole row is the target, like a tab's",
+ snap.hit_at(CX + CW - 1, Y0 + 1) == "newtab",
+ str(snap.hit_at(CX + CW - 1, Y0 + 1)),
+ )
+
+
+def test_a_narrow_sidebar_keeps_the_bare_mark():
+ narrow = _cfg('tab_bar_side "left"\ntab_bar_width 10\n')
+ with Session(SH, cols=90, rows=20, config=narrow) as s:
+ s.settle(40)
+ snap = s.snapshot()
+ row = snap.line(Y0 + 1)[CX : CX + 8]
+ check(
+ "no room for the word, so it is not said", "new tab" not in row, repr(row)
+ )
+ check(
+ "...but the button is still there",
+ snap.hit_at(CX, Y0 + 1) == "newtab",
+ str(snap.hit_at(CX, Y0 + 1)),
+ )
+
+
+def test_the_strip_keeps_its_bare_mark():
+ """The word is a sidebar's affordance, not a new spelling everywhere: in
+ the strip the mark sits at the end of a row of tabs, where columns are
+ scarce and the company it keeps says what it is."""
+ with Session(SH, cols=90, rows=20) as s:
+ s.settle(40)
+ check(
+ "the strip is unchanged",
+ "new tab" not in s.snapshot().line(1),
+ repr(s.snapshot().line(1)),
+ )
+
+
def test_narrow_terminal_falls_back_to_top():
# 40 < width + min_pane cols + 4: the sidebar would leave no room for the
# pane it is chrome for, so the strip goes back to the top row.
@@ -347,6 +389,9 @@ if __name__ == "__main__":
test_the_sidebar_is_framed_like_a_pane()
test_compact_shares_its_lines_with_the_ring()
test_chrome_false_gives_the_columns_back()
+ test_the_sidebar_button_says_what_it_does()
+ test_a_narrow_sidebar_keeps_the_bare_mark()
+ test_the_strip_keeps_its_bare_mark()
test_narrow_terminal_falls_back_to_top()
test_growing_back_restores_the_sidebar()
sys.exit(report())