Skip to content

fix: width-aware input/layout, prompt paste, and FCmd mapper guard#204

Merged
rorygraves merged 1 commit into
mainfrom
fix/unicode-width-paste-fcmd
Jun 1, 2026
Merged

fix: width-aware input/layout, prompt paste, and FCmd mapper guard#204
rorygraves merged 1 commit into
mainfrom
fix/unicode-width-paste-fcmd

Conversation

@rorygraves
Copy link
Copy Markdown
Contributor

Addresses the code-review findings.

Findings addressed

High — Prompt drops bracketed paste. Prompt.handleKey had no InputKey.Paste case and fell through to no-op. It now inserts the first pasted line at the cursor, mirroring TextField's single-line paste behaviour. (TextField and MultiLineInput already gained paste handling in #203, so only Prompt remained.)

High — Input viewport sized by char count, not cells. AnsiRenderer.visibleInput budgeted the fixed prefix and scrolled the editable suffix with length / take / slice, while cursor placement already used WCWidth. CJK/emoji text could overflow the declared lineWidth or scroll incorrectly. The prefix is now clipped and the suffix scrolled by rendered cell width, with new takeCells / charIndexAtCell helpers. For pure-ASCII input the math reduces to the previous behaviour (existing goldens unchanged). nodeExtents' auto-size fallback measures the prompt the same way.

Medium — Layout measured text by String.length. Layout.measureVNode now measures TextNode/InputNode width via WCWidth.stringWidth, so rows, columns, grids, fills, and hit-test zones place wide/combining text consistently with the renderer.

Medium — Cmd.FCmd success mapper unguarded. A throwing toCmd(result) died on the execution context, publishing neither the intended command nor an error and leaving the app stuck loading. The success branch now catches NonFatal and surfaces a TermFlowError.Unexpected, matching the existing failure path.

Medium — supplementary-plane emoji in the diff renderer: already resolved on main by the RenderCell.glyph / renderedGlyph plumbing (drawString stores the full glyph; the diff emits renderedGlyph). No change needed; noting for completeness.

Tests

  • PromptSpec: paste inserts at cursor, keeps only the first line, empty paste is a no-op.
  • AnsiRendererSpec: wide-char input viewport bounded to the declared cell width with the cursor landing correctly.
  • LayoutSpec: measureVNode returns rendered cell width for CJK / emoji / combining text.
  • TuiRuntimeSpec: a throwing FCmd success mapper surfaces a TermFlowError.

sbt ciCheck is green.

Address code-review findings:

- Prompt.handleKey now handles InputKey.Paste (was dropped); inserts the
  first pasted line at the cursor, mirroring TextField. (TextField and
  MultiLineInput already gained paste support in #203.)
- AnsiRenderer.visibleInput sizes the input viewport by rendered cell
  width (WCWidth) instead of UTF-16 length, so CJK/emoji input no longer
  overflows the declared width or scrolls past the cursor. Adds takeCells
  / charIndexAtCell helpers; nodeExtents fallback measured the same way.
- Layout.measureVNode measures TextNode/InputNode width via WCWidth so
  rows, columns, grids, fills, and hit-test zones place wide/combining
  text consistently with the renderer.
- TuiRuntime FCmd now guards the success mapper: if toCmd(result) throws,
  it publishes a TermFlowError instead of dying silently on the EC and
  stranding the app in its loading state.

Tests added for each.

(The supplementary-emoji diff finding was already resolved by the
RenderCell.glyph / renderedGlyph plumbing.)
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 1, 2026

Codecov Report

❌ Patch coverage is 91.11111% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...een/src/main/scala/termflow/tui/AnsiRenderer.scala 87.87% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

@rorygraves rorygraves merged commit abbddb7 into main Jun 1, 2026
2 checks passed
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