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 /HANDOVER.md | |
| 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 'HANDOVER.md')
| -rw-r--r-- | HANDOVER.md | 29 |
1 files changed, 22 insertions, 7 deletions
diff --git a/HANDOVER.md b/HANDOVER.md index 93a8a25..fba59d8 100644 --- a/HANDOVER.md +++ b/HANDOVER.md @@ -47,13 +47,28 @@ prefixes that arm after the pointer rests. Anything that arms on dwell and is so it looks intermittent, which is the worst way to be wrong. This caught me twice, for `edge:` and then `corner:`. -**Glyph width and glyph weight are different problems.** `screen_text` books -every chrome cell as one column. An emoji drawn two columns wide shifts the -whole row. Separately, a glyph that does not *fill* its cell donates that space -to the gap beside it and looks badly spaced next to one that does — the cells -can be provably even and still look wrong. Three attempts at the frame buttons -established this; they are plain ASCII now. If someone reports spacing, dump -the actual cells before touching any arithmetic. +**Glyph width and glyph weight are different problems.** Width is now handled: +`screen_text` and `cells()` ask lib-vt's own table +(`ghostty_unicode_grapheme_width`), the same one the terminal uses for pane +content, so a two-column bell mark claims two columns and the wide cell's +right half is a tail the diff skips. Set `bell_mark "🔔"` and the title row +stays aligned. What is *not* solved is weight: a glyph that does not fill its +cell donates that space to the gap beside it and looks badly spaced next to +one that does — the cells can be provably even and still look wrong. Three +attempts at the frame buttons established this; they are plain ASCII now. If +someone reports spacing, dump the actual cells before touching any arithmetic. + +Two traps found while fixing the width, both worth knowing before touching +this code again. `ghostty_unicode_grapheme_width` measures **only the first +cluster** and tells you how many codepoints it ate; a loop that ignores the +return value and instead asks "did the width change when I added the next +codepoint?" concludes that an entire ASCII string is one cluster, which +silently shrank the cheatsheet modal to ten columns. And a grapheme cluster +can be longer than the 16 bytes a cell holds (a ZWJ family emoji is 18), so +both the cell writer and the config's mark fields clamp on a codepoint +boundary — truncating mid-codepoint put invalid UTF-8 into the snapshot, which +surfaced as a decode error in a test rather than anything resembling the +cause. **The test harness is not a session, and the difference hid a real bug.** The server set `signal(SIGCHLD, SIG_IGN)`, which does not merely skip a handler — |
