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/config.h | |
| 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/config.h')
| -rw-r--r-- | src/config.h | 9 |
1 files changed, 6 insertions, 3 deletions
diff --git a/src/config.h b/src/config.h index 8c01fdb..36e9915 100644 --- a/src/config.h +++ b/src/config.h @@ -110,16 +110,19 @@ typedef struct { * reads as even: a glyph that floats in a mostly-empty cell donates its own * whitespace to the gap beside it and looks further away than its * neighbours, which is what an en dash did here. */ - char zoom_mark[16], zoom_on_mark[16], close_mark[16], min_mark[16]; + /* 32 bytes because a mark is a grapheme cluster, not a character: a + family emoji is eighteen bytes on its own. Truncating one mid- + codepoint used to put invalid UTF-8 on the wire. */ + char zoom_mark[32], zoom_on_mark[32], close_mark[32], min_mark[32]; /* The new-tab button at the end of the strip. Drawn with a space each side, * so a one-character mark is still a three-cell target. */ - char newtab_mark[16]; + char newtab_mark[32]; bool bell_indicator; /* What to draw. A short string rather than a fixed glyph: the obvious * choice is an emoji bell, and emoji are drawn two columns wide by many * terminals while chrome here is booked as one — so the default is narrow * and anyone who knows their terminal can say otherwise. */ - char bell_mark[16]; + char bell_mark[32]; /* Which dead panes stay, showing what they printed and offering to run * again, and which just go. * |
