aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-22 15:01:07 +0200
committeruser <user@clank>2026-09-22 15:01:07 +0200
commit43ab0d0c0dc0c79f04c7e70f5ab3b22b3e36e09a (patch)
treeba1a5960438c15391fbf1c0a4e0c49421a8c1fcb /src
parentsidebar: align the numbers by default (diff)
downloadslosh-43ab0d0c0dc0c79f04c7e70f5ab3b22b3e36e09a.tar.gz
slosh-43ab0d0c0dc0c79f04c7e70f5ab3b22b3e36e09a.tar.xz
slosh-43ab0d0c0dc0c79f04c7e70f5ab3b22b3e36e09a.zip
sidebar: air inside it, the way panes have padding (tab_bar_padding)
`padding` is a pane's: the ring between a frame and the text it holds, in rows times gap_aspect so a `1` looks square around a block of prose. Handing the same knob to the sidebar looked tempting and is wrong twice over. The sidebar is chrome, not a pane, and its width is fixed by tab_bar_width -- so `padding 1` would quietly eat four of a sixteen-column strip (two cells a side, aspect included) and shorten every tab name, because somebody wanted their terminal text to breathe. And its two axes are not one idea: the columns are an indent for labels, the rows are air above a list. So tab_bar_padding, its own knob in the family the rest of the sidebar's air already lives in (tab_bar_pad, tab_gap, newtab_pad), taking the same 1/2/4 CSS-order values as `padding` -- one reader for both, so they cannot drift into meaning different things -- but counted in cells. The default `0 1` is what the sidebar already drew: that one column is where every label's leading space has been hard-coded since the sidebar existed, and moving it into a setting is what makes `tab_bar_padding 0` possible at all -- names flush against the frame, which was not previously a thing anyone could ask for. The statuses, the `+ new tab` row and the pane count follow the same indent, since they were each carrying their own copy of that space. It is air *inside* the plate: a tab's fill still spans the whole row and the whole row is still the hit, because the row is what you click and a highlight that stopped short of the frame would read as a highlighted word rather than a chosen row. The bottom rows are given up when the strip is short of them, like every other piece of air in here. tab_bar_pad is the older name for the top component alone. It still works, still means exactly that, and wins where a config writes both -- it is the more specific of the two. The dump writes tab_bar_padding and mentions it.
Diffstat (limited to 'src')
-rw-r--r--src/app_draw.c59
-rw-r--r--src/config.c102
2 files changed, 116 insertions, 45 deletions
diff --git a/src/app_draw.c b/src/app_draw.c
index 85b5b6c..cf3ff39 100644
--- a/src/app_draw.c
+++ b/src/app_draw.c
@@ -1844,23 +1844,32 @@ static uint16_t draw_tab_cell(app_t *a, screen_t *s, size_t i, uint16_t x,
* spare the columns: on a narrow one the name is what you came for, so the
* number goes back in front where it costs two cells rather than a column.
* Never while renaming, where the whole cell is the editor. */
+ /* In a sidebar the label's indent is the strip's own left padding, and its
+ * right padding is cells the name may not have; in the top strip a tab is
+ * as wide as its label and the single space each side *is* the plate. */
+ uint16_t padl = max_w ? CFG.tab_pad_left : 1;
+ uint16_t padr = max_w ? CFG.tab_pad_right : 1;
+
char idx[16] = "";
bool right_idx = false;
if (max_w && !editing && CFG.tab_bar_index == TAB_INDEX_RIGHT) {
- snprintf(idx, sizeof idx, "%zu ", i + 1);
+ snprintf(idx, sizeof idx, "%zu%*s", i + 1, (int)padr, "");
uint16_t iw = (uint16_t)cells(idx);
right_idx = max_w >= (uint16_t)(iw + 5);
if (!right_idx) idx[0] = 0;
}
if (editing)
- snprintf(label, sizeof label, " %s\u2588 ", a->rename_buf);
+ snprintf(label, sizeof label, "%*s%s\u2588%*s", (int)padl, "",
+ a->rename_buf, (int)padr, "");
else if (right_idx)
- snprintf(label, sizeof label, " %s", nm[0] ? nm : "");
+ snprintf(label, sizeof label, "%*s%s", (int)padl, "", nm[0] ? nm : "");
else if (nm[0])
- snprintf(label, sizeof label, " %zu:%s ", i + 1, nm);
+ snprintf(label, sizeof label, "%*s%zu:%s%*s", (int)padl, "", i + 1, nm,
+ (int)padr, "");
else
- snprintf(label, sizeof label, " %zu ", i + 1);
+ snprintf(label, sizeof label, "%*s%zu%*s", (int)padl, "", i + 1, (int)padr,
+ "");
/* A pinned width cuts the label to fit -- bell and number budgeted first,
* so neither is what a long name eats -- and then pads it out, so the
@@ -1988,10 +1997,13 @@ static uint16_t draw_tab_cell(app_t *a, screen_t *s, size_t i, uint16_t x,
static void draw_newtab_button(app_t *a, screen_t *s, uint16_t x, uint16_t y,
uint16_t max_w, bool dragging_pane) {
char btn[64];
+ /* The same indent the labels take, so the button lines up with the list it
+ * sits under rather than with the frame. */
+ int padl = max_w ? (int)CFG.tab_pad_left : 1;
if (max_w && (uint16_t)(cells(CFG.newtab_mark) + 10) <= max_w)
- snprintf(btn, sizeof btn, " %s new tab ", CFG.newtab_mark);
+ snprintf(btn, sizeof btn, "%*s%s new tab ", padl, "", CFG.newtab_mark);
else
- snprintf(btn, sizeof btn, " %s ", CFG.newtab_mark);
+ snprintf(btn, sizeof btn, "%*s%s ", padl, "", 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) pad_cells(btn, sizeof btn, max_w);
@@ -2217,7 +2229,22 @@ void draw_tab_sidebar(app_t *a, screen_t *s) {
* above the list. The pad is where the drawing starts, not a layout fact:
* the panes' rect is the same with pad 0 and pad 5, so tuning it repaints
* without reflowing anyone. */
- uint16_t y = (uint16_t)(top + (chrome ? 1 : 0) + CFG.tab_bar_pad);
+ uint16_t y = (uint16_t)(top + (chrome ? 1 : 0) + CFG.tab_pad_top);
+
+ /* The indent everything on a row starts at, and the width it has to work
+ * in. Inside the plate, not outside it: a tab's fill still spans the whole
+ * row, because the row is what you click and a highlight that stopped short
+ * of the frame would read as a highlighted word. */
+ uint16_t ix = (uint16_t)(cx + (cw > CFG.tab_pad_left ? CFG.tab_pad_left : 0));
+
+ /* Air at the bottom, taken before anything is placed against the bottom
+ * edge -- and given up when the rows are not there to spare, like every
+ * other piece of air in this strip. */
+ {
+ uint16_t pad = CFG.tab_pad_bottom;
+ while (pad && limit <= (uint16_t)(y + pad)) pad--;
+ limit = (uint16_t)(limit - pad);
+ }
/* Bottom rows first: what the horizontal strip keeps on its right. Indented
* one cell to sit under the labels' own leading space -- at cx exactly, the
@@ -2228,15 +2255,14 @@ void draw_tab_sidebar(app_t *a, screen_t *s) {
snprintf(info, sizeof info, "%zu pane%s", np, np == 1 ? "" : "s");
if (limit > y && (uint16_t)(strlen(info) + 1) <= cw) {
limit--;
- screen_text(s, (uint16_t)(cx + 1), limit, info, TAB_COUNT, NO_COLOR, 0);
+ screen_text(s, ix, limit, info, TAB_COUNT, NO_COLOR, 0);
}
if (a->prefix) { /* the prefix is a mode: say so, and say which key */
char pfx[24];
config_chord_name(CFG.prefix_key, CFG.prefix_mods, pfx, sizeof pfx);
if (limit > y && (uint16_t)(cells(pfx) + 1) <= cw) {
limit--;
- screen_text(s, (uint16_t)(cx + 1), limit, pfx, PREFIX_FG, PREFIX_BG,
- ATTR_BOLD);
+ screen_text(s, ix, limit, pfx, PREFIX_FG, PREFIX_BG, ATTR_BOLD);
}
}
@@ -2298,8 +2324,10 @@ void draw_tab_sidebar(app_t *a, screen_t *s) {
size_t lines = CFG.tab_bar_status_lines ? CFG.tab_bar_status_lines : 1;
if (lines > STATUS_WRAP_MAX) lines = STATUS_WRAP_MAX;
if (lines > allow) lines = allow;
- /* The text column: everything but the leading space below. */
- uint16_t tw = cw > 1 ? (uint16_t)(cw - 1) : 1;
+ /* The text column: the row minus the strip's own padding, which is what
+ * puts a status in the column its tab's name starts in. */
+ uint16_t inset = (uint16_t)(CFG.tab_pad_left + CFG.tab_pad_right);
+ uint16_t tw = cw > inset ? (uint16_t)(cw - inset) : 1;
/* Lay the statuses out before drawing any, because whether the last row
* has to become an ellipsis depends on what did not fit -- and with
@@ -2360,7 +2388,8 @@ void draw_tab_sidebar(app_t *a, screen_t *s) {
* sidebar, where three columns is a fifth of everything a status has
* to say itself in. The slant below says the same thing in none of
* them, and the band says which pane said it. */
- snprintf(text, sizeof text, " %s", rows[j][k]);
+ snprintf(text, sizeof text, "%*s%s", (int)CFG.tab_pad_left, "",
+ rows[j][k]);
pad_cells(text, sizeof text, cw); /* so a themed band covers the row */
/* Italic, and ambient in colour: these are annotations until pointed
* at. The slant is what separates them from the labels, rather than
@@ -2377,7 +2406,7 @@ void draw_tab_sidebar(app_t *a, screen_t *s) {
}
if (ell && y < limit && used < allow) {
char text[STATUS_ROW_CAP + 8];
- snprintf(text, sizeof text, " \u2026");
+ snprintf(text, sizeof text, "%*s\u2026", (int)CFG.tab_pad_left, "");
pad_cells(text, sizeof text, cw);
screen_text(s, cx, y, text, TAB_STATUS_FG, TAB_STATUS_BG, ATTR_ITALIC);
y++; /* an ellipsis is not a door: no hit */
diff --git a/src/config.c b/src/config.c
index a1d8880..81f23d5 100644
--- a/src/config.c
+++ b/src/config.c
@@ -311,6 +311,40 @@ static void complain(config_t *c, char *err, size_t errcap, int line,
if (err && errcap && !err[0]) snprintf(err, errcap, "%s", full);
}
+/* `x 1`, `x 0 2`, or `x 1 2 1 2` -- one value for every side, two for
+ * vertical and horizontal, four in CSS order (top, right, bottom, left),
+ * because that is the order everybody who has written a stylesheet already
+ * knows. Three is refused rather than guessed at: CSS says
+ * top/horizontal/bottom and a reader who has to look that up is a reader who
+ * cannot see what the line does.
+ *
+ * One reader for every setting shaped like this, so `padding` and
+ * `tab_bar_padding` cannot drift into meaning two different things. */
+static void parse_sides(config_t *c, const kdl_node_t *n, const char *name,
+ uint16_t *top, uint16_t *right, uint16_t *bottom,
+ uint16_t *left, char *err, size_t errcap) {
+ if (!n) return;
+ long v[4] = {0};
+ size_t k = n->nargs < 4 ? n->nargs : 4;
+ for (size_t i = 0; i < k; i++) v[i] = kdl_arg_int(n, i, 0);
+ if (n->nargs == 1) {
+ *top = *right = *bottom = *left = (uint16_t)v[0];
+ } else if (n->nargs == 2) {
+ *top = *bottom = (uint16_t)v[0];
+ *right = *left = (uint16_t)v[1];
+ } else if (n->nargs == 4) {
+ *top = (uint16_t)v[0];
+ *right = (uint16_t)v[1];
+ *bottom = (uint16_t)v[2];
+ *left = (uint16_t)v[3];
+ } else {
+ complain(c, err, errcap, n->line,
+ "%s takes 1, 2 or 4 values (all, vertical horizontal, or top "
+ "right bottom left), not %zu",
+ name, n->nargs);
+ }
+}
+
size_t config_messages(const config_t *c, const char **out, size_t max) {
size_t n = c->nmsgs < max ? c->nmsgs : max;
for (size_t i = 0; i < n; i++) out[i] = c->msgs[i];
@@ -959,7 +993,11 @@ void config_defaults(config_t *c) {
* standing in for, and a blank row inside the frame on top of it reads as
* a gap nobody asked for. Still there for a bare sidebar, or for anyone
* who wants room at the top for a borderless window's own buttons. */
- c->tab_bar_pad = 0;
+ /* No rows of air by default, and one column: that column is where every
+ * label's leading space used to be hard-coded, so the default draws what it
+ * always drew and 0 is now a thing you can ask for. */
+ c->tab_pad_top = c->tab_pad_bottom = 0;
+ c->tab_pad_left = c->tab_pad_right = 1;
c->tab_gap = 0; /* flush, the way the list has always read */
/* Three is enough for "the build, the tests, and one more thing" without a
* chatty tab pushing the list off the bottom. */
@@ -1716,8 +1754,24 @@ char *config_render(const config_t *c) {
c->tab_bar_index == TAB_INDEX_RIGHT ? "right" : "prefix");
cb_add(&b, "tab_bar_width %u // columns a sidebar takes\n",
c->tab_bar_width);
- cb_add(&b, "tab_bar_pad %u // blank rows above a sidebar's tabs\n",
- c->tab_bar_pad);
+ if (c->tab_pad_top == c->tab_pad_right &&
+ c->tab_pad_top == c->tab_pad_bottom && c->tab_pad_top == c->tab_pad_left)
+ cb_add(&b, "tab_bar_padding %u // air inside a sidebar, in cells\n",
+ c->tab_pad_top);
+ else if (c->tab_pad_top == c->tab_pad_bottom &&
+ c->tab_pad_right == c->tab_pad_left)
+ cb_add(&b,
+ "tab_bar_padding %u %u // air inside a sidebar: rows, then "
+ "columns\n",
+ c->tab_pad_top, c->tab_pad_right);
+ else
+ cb_add(&b, "tab_bar_padding %u %u %u %u // top right bottom left\n",
+ c->tab_pad_top, c->tab_pad_right, c->tab_pad_bottom,
+ c->tab_pad_left);
+ cb_add(&b,
+ "// tab_bar_pad %u // the top of that alone, the older "
+ "name for it\n",
+ c->tab_pad_top);
cb_add(&b, "tab_gap %u // blank rows between a sidebar's tabs\n",
c->tab_gap);
cb_add(&b,
@@ -2200,6 +2254,7 @@ static const char *const KNOWN_TOP[] = {
"status_pad",
"tab_bar_chrome",
"tab_bar_pad",
+ "tab_bar_padding",
"tab_bar_index",
"tab_bar_side",
"tab_bar_status",
@@ -2571,31 +2626,20 @@ static bool load_into(config_t *c, const char *path, int depth, char *err,
* stylesheet already knows. Three is refused rather than guessed at: CSS says
* top/horizontal/bottom and a reader who has to look that up is a reader who
* cannot see what the line does. */
- {
- const kdl_node_t *pn = kdl_child(root, "padding");
- if (pn) {
- long v[4];
- size_t n = pn->nargs < 4 ? pn->nargs : 4;
- for (size_t i = 0; i < n; i++) v[i] = kdl_arg_int(pn, i, 0);
- if (pn->nargs == 1) {
- c->pad_top = c->pad_right = c->pad_bottom = c->pad_left =
- (uint16_t)v[0];
- } else if (pn->nargs == 2) {
- c->pad_top = c->pad_bottom = (uint16_t)v[0];
- c->pad_right = c->pad_left = (uint16_t)v[1];
- } else if (pn->nargs == 4) {
- c->pad_top = (uint16_t)v[0];
- c->pad_right = (uint16_t)v[1];
- c->pad_bottom = (uint16_t)v[2];
- c->pad_left = (uint16_t)v[3];
- } else {
- complain(c, err, errcap, pn->line,
- "padding takes 1, 2 or 4 values (all, vertical horizontal, or "
- "top right bottom left), not %zu",
- pn->nargs);
- }
- }
- }
+ parse_sides(c, kdl_child(root, "padding"), "padding", &c->pad_top,
+ &c->pad_right, &c->pad_bottom, &c->pad_left, err, errcap);
+ /* The same shape for the sidebar's own air, so one form is learned once.
+ * In cells rather than rows-times-gap_aspect: a sidebar's columns and rows
+ * are doing different jobs (an indent, and air above a list), and scaling
+ * one of them by the cell aspect would make `2` mean four columns in a
+ * sixteen-column strip. */
+ parse_sides(c, kdl_child(root, "tab_bar_padding"), "tab_bar_padding",
+ &c->tab_pad_top, &c->tab_pad_right, &c->tab_pad_bottom,
+ &c->tab_pad_left, err, errcap);
+ /* After it, because it is the more specific of the two: a config that says
+ * both means the narrow one where they overlap. */
+ c->tab_pad_top =
+ (uint16_t)kdl_arg_int(kdl_child(root, "tab_bar_pad"), 0, c->tab_pad_top);
c->rounded = kdl_arg_bool(kdl_child(root, "rounded"), 0, c->rounded);
c->compact = kdl_arg_bool(kdl_child(root, "compact"), 0, c->compact);
c->status_bar = kdl_arg_bool(kdl_child(root, "status_bar"), 0, c->status_bar);
@@ -2624,8 +2668,6 @@ static bool load_into(config_t *c, const char *path, int depth, char *err,
}
c->tab_bar_width = (uint16_t)kdl_arg_int(kdl_child(root, "tab_bar_width"), 0,
c->tab_bar_width);
- c->tab_bar_pad =
- (uint16_t)kdl_arg_int(kdl_child(root, "tab_bar_pad"), 0, c->tab_bar_pad);
c->tab_gap = (uint16_t)kdl_arg_int(kdl_child(root, "tab_gap"), 0, c->tab_gap);
c->tab_bar_status = (uint16_t)kdl_arg_int(kdl_child(root, "tab_bar_status"),
0, c->tab_bar_status);