Skip to content

Widget display width - #212

Merged
rorygraves merged 1 commit into
mainfrom
task/widget-display-width
Jun 15, 2026
Merged

rorygraves merged 1 commit into
mainfrom
task/widget-display-width

Conversation

@rorygraves

Copy link
Copy Markdown
Contributor

DECISIONS — widget-display-width (#206)

Follow-up to #205. Converted the last two widgets that still sized text by UTF-16
String.length to measure by display cells (WCWidth).

Changes

MenuBar.scala

  • dropdownInnerWidth: _.lengthWCWidth.stringWidth (widest item by cells).
  • dropdown rows: item.padTo(innerWidth, ' ')fitToCells(item, innerWidth)
    (cell-aware clip + pad; ASCII unchanged).
  • titleStartCol / hitTestTitles: title.length + 4WCWidth.stringWidth(title) + 4.
    The +4 (2-cell opener + 2-cell closer, same for [ X ] and X ) is unchanged;
    only the title measurement moved to cells, so a wide-char title no longer
    mis-positions later dropdowns or mislands title hit-testing.

ListView.scala

  • view: content.take(width-2).padTo(width-2, ' ')fitToCells(content, width-2).
    Old .take could slice a surrogate pair / wide glyph at the boundary and could
    overflow the row (UTF-16 count < display cells for wide content).

Key decision: helpers duplicated, not centralised

The #205 clipToCells / fitToCells are private to Table, and #205 itself
already duplicated the same logic privately into TextField and MultiLineInput
(clipToWidth/padToWidth). I followed that established per-widget-private pattern
rather than extracting a shared util — keeps this change minimal and consistent with
the codebase as it stands. The copies in MenuBar/ListView are byte-identical to
Table's. If a future task wants one home for these, WCWidth (termflow-terminal) is
the natural place; that refactor is out of scope here.

Tests

  • MenuBarSpec: wide-title (文件) hit-testing positions the next menu by cells;
    dropdown rows with a wide item (打开) pad to display width; open dropdown aligns
    under the correct menu. (Old .length math would have placed Edit at col 6 not 10.)
  • ListViewSpec: CJK row aligns to the grid; wide content truncates on a glyph
    boundary (odd content width can't fit a half glyph); emoji (surrogate pairs)
    truncate without leaving a lone surrogate half.
  • Rendered-cell assertions reconstruct logical content via RenderCell.renderedGlyph
    (skipping the width-0 filler the renderer inserts after a wide glyph), since the raw
    .ch grid interleaves a space after each 2-cell glyph.
  • Full termflowWidgets/test green (309 tests).

Risk

Low. Behaviour identical for pure-ASCII (stringWidth == length, fitToCells ==
take+padTo). WCWidth width tables (ambiguous-width = 1, curated emoji ranges)
are inherited from #205 and unchanged.

MenuBar and ListView still measured text by UTF-16 String.length instead
of display cells, so CJK/emoji content misaligned columns and could split a
wide glyph or surrogate pair at a truncation boundary.

- MenuBar: dropdownInnerWidth, dropdown row padding, titleStartCol and
  hitTestTitles now use WCWidth.stringWidth / cell-aware fitToCells.
- ListView: row truncate+pad now uses fitToCells instead of take/padTo.

Reuses the cell-aware clipToCells/fitToCells helpers from #205 (kept
per-widget-private, matching the existing pattern). ASCII behaviour
unchanged. Adds CJK/emoji regression specs.
@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.93939% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...src/main/scala/termflow/tui/widgets/ListView.scala 93.33% 1 Missing ⚠️
.../src/main/scala/termflow/tui/widgets/MenuBar.scala 94.44% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@rorygraves
rorygraves merged commit b852a90 into main Jun 15, 2026
2 checks passed
@rorygraves
rorygraves deleted the task/widget-display-width branch June 15, 2026 04:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant