diff options
| author | user <user@clank> | 2026-08-14 16:38:26 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-14 16:38:26 +0200 |
| commit | 6548577400ab6833b9424de056c6da6079ed6ff4 (patch) | |
| tree | b2900cbb0e272f18024d95596153da42f6196c24 /src | |
| parent | sl0ppty: a collapsed pane is drawn as a pane, closed (diff) | |
| download | slosh-6548577400ab6833b9424de056c6da6079ed6ff4.tar.gz slosh-6548577400ab6833b9424de056c6da6079ed6ff4.tar.xz slosh-6548577400ab6833b9424de056c6da6079ed6ff4.zip | |
sl0ppty: a status line along the bottom
The strip along the top answers "which tab" and "how many panes" — questions
about what you could switch to. Nothing answered "what am I looking at", and
three of the states a pane can be in were discoverable only by being surprised
by them.
So the bottom line is about the focused pane and deliberately repeats nothing
from the top: the session it lives in, the tab, what the pane calls itself
(its purpose if it declared one, otherwise its title), and on the right, the
state worth knowing — scrolled back and how far, on an alternate screen, not
started yet, or exited. Right side budgeted first, the same rule the tab strip
and the pane frame already use, so a long name cannot push the state off the
end.
The session name had to be plumbed in: the server knows it because it opened
the socket under that name, and the app had no reason to until now.
Reserving a row is not free and three tests noticed, which is the correct
number to notice. Two were arithmetic — a chrome budget written as rows-5 and a
six-row session whose pane no longer had room for a frame at all. The third was
more useful: test_tiny_panes_have_no_targets asserted "no border hit that is
one row tall and under four wide", which is a description of the shape a
collapsed header used to have rather than of the thing being tested, and it
started catching the perfectly legitimate side border of a pane that was merely
short. It now says what it means — no split targets on a *header* row — and is
both stricter and immune to the layout getting a row shorter again.
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.c | 80 | ||||
| -rw-r--r-- | src/app.h | 4 | ||||
| -rw-r--r-- | src/config.c | 3 | ||||
| -rw-r--r-- | src/config.h | 3 | ||||
| -rw-r--r-- | src/server.c | 1 |
5 files changed, 89 insertions, 2 deletions
@@ -177,6 +177,10 @@ struct app { int64_t name_click_ms; uint32_t name_click_id; const char *const *argv; + /* What this session is called. The server knows it because it opened the + * socket under that name; the app only knows it because it is worth saying + * out loud when several are running. */ + char session[64]; /* the screen we last composed into: its hit list is what a click resolves * against, so routing can never consult geometry the user never saw */ const screen_t *painted; @@ -266,6 +270,72 @@ size_t app_toast_count(app_t *a) { return a->ntoasts; } +/* The line along the bottom: what you are looking at, rather than what you + * could switch to. The strip along the top already answers "which tab" and + * "how many panes"; repeating that here would spend a row saying it twice. + * + * So this one is about the focused pane: which session it lives in, which tab, + * what it calls itself, and whether it is in a state worth knowing about — + * scrolled back, on an alternate screen, or not started yet. Those last three + * are the ones you can otherwise only discover by being surprised. */ +static void draw_status_line(app_t *a, screen_t *s) { + if (!CFG.status_line || s->rows < 3) return; + uint16_t y = (uint16_t)(s->rows - CFG.gap - 1); + uint16_t x = (uint16_t)(CFG.gap * CFG.gap_aspect); + uint16_t right = (uint16_t)(s->cols > x ? s->cols - x : 0); + if (right <= x) return; + + node_t *f = a->ntabs ? cur(a)->focus : NULL; + + /* Right side first, so a long name can never push the state off the end — + * the same budgeting rule the tab strip and the pane frame both use. */ + char ind[64] = {0}; + if (f) { + if (pane_suspended(f->pane)) { + snprintf(ind, sizeof ind, "not started"); + } else if (!pane_alive(f->pane)) { + snprintf(ind, sizeof ind, "exited"); + } else if (pane_scrolled(f->pane)) { + uint32_t above = 0, total = 0; + pane_scroll_pos(f->pane, &above, &total); + snprintf(ind, sizeof ind, "\u25b2%u scrolled", total > above ? total - above : 0); + } else if (pane_alt_screen(f->pane)) { + snprintf(ind, sizeof ind, "alt screen"); + } + } + if (ind[0]) { + uint16_t iw = (uint16_t)strlen(ind); + if (right > x + iw + 2) { + screen_text(s, (uint16_t)(right - iw), y, ind, TITLE_FOCUS, NO_COLOR, + ATTR_BOLD); + right = (uint16_t)(right - iw - 1); + } + } + + char line[256]; + size_t n = 0; + if (a->session[0]) + n += (size_t)snprintf(line + n, sizeof line - n, "%s", a->session); + if (a->ntabs) { + const tab_t *t = &a->tabs[a->cur]; + const char *tn = t->name[0] ? t->name : (t->purpose[0] ? t->purpose : NULL); + if (tn) + n += (size_t)snprintf(line + n, sizeof line - n, "%s%s", + n ? " \u00b7 " : "", tn); + } + if (f) { + const char *pt = pane_title(f->pane); + const char *pp = f->purpose[0] ? f->purpose : NULL; + if (pp) + n += (size_t)snprintf(line + n, sizeof line - n, "%s%s", n ? " \u00b7 " : "", pp); + else if (pt && *pt) + n += (size_t)snprintf(line + n, sizeof line - n, "%s%s", n ? " \u00b7 " : "", pt); + } + if (!n) return; + if (n > (size_t)(right - x)) line[right - x] = 0; + screen_text(s, x, y, line, FRAME_IDLE, NO_COLOR, 0); +} + static void draw_toasts(app_t *a, screen_t *s) { toasts_expire(a); if (!a->ntoasts) return; @@ -705,6 +775,7 @@ static void layout_node(node_t *n, rect_t r, layout_ctx_t *ctx) { } #define STRIP_ROWS (CFG.status_bar ? 1 : 0) +#define LINE_ROWS (CFG.status_line ? 1 : 0) static void layout(app_t *a) { if (!a->ntabs) return; @@ -713,7 +784,9 @@ static void layout(app_t *a) { rect_t r = {.x = gx, .y = top, .w = (uint16_t)(a->cols > 2 * gx ? a->cols - 2 * gx : a->cols), - .h = (uint16_t)(a->rows > top + gy ? a->rows - top - gy : 1)}; + .h = (uint16_t)(a->rows > top + gy + LINE_ROWS + ? a->rows - top - gy - LINE_ROWS + : 1)}; node_t *root = cur(a)->root; if (!root) return; @@ -1018,6 +1091,10 @@ static node_t *pane_by_id(app_t *a, uint32_t id) { return b.found; } +void app_set_session(app_t *a, const char *name) { + snprintf(a->session, sizeof a->session, "%s", name ? name : ""); +} + bool app_focus_pane(app_t *a, uint32_t id) { node_t *n = pane_by_id(a, id); if (!n) return false; @@ -2118,6 +2195,7 @@ void app_compose(app_t *a, screen_t *s) { layout(a); if (CFG.status_bar) draw_tab_strip(a, s); draw_node(a, s, cur(a)->root); + draw_status_line(a, s); if (a->finder) draw_finder(a, s); /* painted last, so its hits win */ draw_toasts(a, s); /* and above even that: it is transient */ } @@ -83,6 +83,10 @@ bool app_apply_layout_text(app_t *a, const char *text, bool replace, char *err, bool app_apply_layout_file(app_t *a, const char *path, bool replace, char *err, size_t errcap); +/* What this session is called, for the status line. The server knows it + * because it opened the socket under that name. */ +void app_set_session(app_t *a, const char *name); + /* Address panes by id (the control API), not "the focused one". */ bool app_focus_pane(app_t *a, uint32_t id); bool app_split_pane(app_t *a, uint32_t id, bool rows); diff --git a/src/config.c b/src/config.c index f7b1179..e0a40fe 100644 --- a/src/config.c +++ b/src/config.c @@ -178,6 +178,7 @@ void config_defaults(config_t *c) { c->hover_delay_ms = 250; c->double_click_ms = 400; c->status_bar = true; + c->status_line = true; c->focus_follows_mouse = true; c->default_fg = rgb(0xff, 0xff, 0xff); @@ -294,6 +295,8 @@ bool config_load(config_t *c, const char *path, char *err, size_t errcap) { c->pad = (uint16_t)kdl_arg_int(kdl_child(root, "padding"), 0, c->pad); c->rounded = kdl_arg_bool(kdl_child(root, "rounded"), 0, c->rounded); c->status_bar = kdl_arg_bool(kdl_child(root, "status_bar"), 0, c->status_bar); + c->status_line = + kdl_arg_bool(kdl_child(root, "status_line"), 0, c->status_line); c->focus_follows_mouse = kdl_arg_bool(kdl_child(root, "focus_follows_mouse"), 0, c->focus_follows_mouse); diff --git a/src/config.h b/src/config.h index 30b5005..a85a204 100644 --- a/src/config.h +++ b/src/config.h @@ -110,7 +110,8 @@ typedef struct { * already had to avoid by hand. */ shader_t state_shaders[PSTATE_COUNT][SHADE_MAX]; size_t state_n[PSTATE_COUNT]; - bool status_bar; + bool status_bar; /* the strip along the top: tabs, prefix, pane count */ + bool status_line; /* the line along the bottom: what you are looking at */ bool focus_follows_mouse; /* What a cell's "terminal default" colour resolves to when a shader has to diff --git a/src/server.c b/src/server.c index b372821..08c944b 100644 --- a/src/server.c +++ b/src/server.c @@ -188,6 +188,7 @@ int server_run(const char *name, const char *const argv[], uint16_t cols, server_t s = {0}; s.app = app_new(argv, cols, rows); + if (s.app) app_set_session(s.app, name); if (!s.app) return 1; if (layout) { char err[256] = {0}; |
