aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-15 18:44:07 +0200
committeruser <user@clank>2026-08-15 18:44:07 +0200
commit93ce0b645babd0851edb34701bbfffa5565daf65 (patch)
treeb402f934e5624a731fef4988d5d4e640766817fa /src/app.c
parentsl0ppty: watch the config directory even when it is not there yet (diff)
downloadslosh-93ce0b645babd0851edb34701bbfffa5565daf65.tar.gz
slosh-93ce0b645babd0851edb34701bbfffa5565daf65.tar.xz
slosh-93ce0b645babd0851edb34701bbfffa5565daf65.zip
sl0ppty: measure chrome in columns, not characters
Good catch: a two-column bell mark shifted the pane title row. Everything in app.c that positions chrome -- title budgets, button placement, the hit rects that have to agree with them -- came from a `cells()` that counted codepoints, and a glyph drawn in two columns was booked as one, so every glyph after it landed a column early. Widths now come from lib-vt's own table, via ghostty_unicode_grapheme_width. That is deliberately the same table the terminal uses for pane content, so chrome and content cannot disagree about how wide something is, and it is cluster-aware: an emoji with a variation selector is two codepoints and one glyph. A wide cell claims its right half as a tail (width 0), which the renderer already knew how to skip. HANDOVER said this was a known trap and to dump the actual cells before touching arithmetic, which is exactly how this went: the first assertion I wrote passed against a deliberately broken width function, because it measured the frame's right corner -- drawn at an absolute position, so it does not move -- rather than the columns the mark claims. The test now reads the mark's own colour runs and demands 1 column for a narrow mark and 2 for a wide one, and it fails when the width function is stubbed back to 1. Two things fell out on the way: ghostty_unicode_grapheme_width measures only the *first* cluster and returns the codepoints it consumed. My first loop ignored that and asked "did the width change when I added the next codepoint?", which concludes that an entire ASCII string is a single cluster -- the cheatsheet modal collapsed to ten columns wide with the text clipped. Use the return value. A cluster can also be longer than a cell holds: a ZWJ family emoji is 18 bytes against 16. Both the cell writer and the config's mark fields (now 32 bytes) clamp on a codepoint boundary, because snprintf cuts at a byte and half a codepoint is invalid UTF-8 -- which showed up as a decoder error in a test far from the cause, not as a short mark.
Diffstat (limited to 'src/app.c')
-rw-r--r--src/app.c16
1 files changed, 7 insertions, 9 deletions
diff --git a/src/app.c b/src/app.c
index 4b86fb2..83cb125 100644
--- a/src/app.c
+++ b/src/app.c
@@ -305,15 +305,13 @@ static tab_t *cur(app_t *a) { return &a->tabs[a->cur]; }
* Always asked with the rect that is about to be registered as the hit, so a
* thing that lights up and the thing that would be clicked cannot drift apart.
* A pointer already carrying something is busy and lights nothing. */
-/* Display columns of a short chrome string. Counts codepoints, which is exact
- * for what chrome draws: every glyph here is one column, and a mark wide
- * enough not to be is the thing the config warns you about. */
-static uint16_t cells(const char *str) {
- uint16_t n = 0;
- for (const char *q = str; q && *q; q++)
- if (((unsigned char)*q & 0xC0) != 0x80) n++;
- return n;
-}
+/* Display columns of a chrome string, from the terminal's own width table.
+ *
+ * This counted codepoints until a two-column bell mark shifted a pane's title
+ * row: booking one column for a glyph drawn in two moves everything after it,
+ * and every length in this file -- title budgets, button positions, the hit
+ * rects that must agree with them -- is computed from here. */
+static uint16_t cells(const char *str) { return screen_cells(str); }
static bool ptr_on(const app_t *a, uint16_t x, uint16_t y, uint16_t w,
uint16_t h) {