diff options
| author | user <user@clank> | 2026-08-15 18:44:07 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-15 18:44:07 +0200 |
| commit | 93ce0b645babd0851edb34701bbfffa5565daf65 (patch) | |
| tree | b402f934e5624a731fef4988d5d4e640766817fa /src/app.c | |
| parent | sl0ppty: watch the config directory even when it is not there yet (diff) | |
| download | slosh-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.c | 16 |
1 files changed, 7 insertions, 9 deletions
@@ -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) { |
