* fix(terminal): render the covered row tail inside the IME preedit overlay
Closes#12545.
Composing mid-line hid the character at the cursor for the whole composition.
The preedit overlay is an opaque box anchored to the cursor cell, and nothing
reaches the pty while composing, so those cells still held their characters —
the box simply covered them.
`CompositionHelper` now draws the rest of the row after the preedit inside the
view, so the composition reads as inserted text pushing the tail right. Four
details come with it:
- The view is start-anchored while it carries a tail, so the preedit stays put
and the pushed tail clips at the right edge; alone, `rtl` still keeps a long
preedit's end in view.
- It is themed from `options.theme` instead of the stock `#000`/`#FFF`, with any
alpha dropped — the view masks the cells it draws over, so a see-through
background would re-expose the very characters the tail stands in for.
- The helper textarea syncs to the preedit's own bounds, so IME candidate
dialogs anchor to the composing text rather than past the rendered tail.
- A TUI can repaint the row under an open composition, so
`updateCompositionElements` — which already runs on every render — re-reads
the remainder and re-renders on change. A string compare adds no layout read.
The tail is read with an explicit end column: the cacheable form of
`translateToString` arms the line string cache's self-renewing idle-clear timer,
and the composition path must own no timers.
Geometry is not the cause. Two mature reference terminal implementations compose
marked text into the grid rather than into a floating box, and both still blank
the cells under it — one of them literally substitutes the marked characters
into the row's character array before rasterizing. Moving off the overlay would
not have fixed this report; rendering the covered tail is what does.
The e2e arm asserts the invariant an opaque overlay owes the grid: it must
render every committed cell its bounding rect covers. That is measured from the
real rect against the real cell grid, so it fails on the unfixed build with
`covers "하" / renders "가"`.
Known limitation: the rendered tail is plain-styled while composing (theme
foreground on theme background, no per-cell colors); colors return on commit.
This is inherent to the overlay, and drawing the preedit into the cell renderer
instead would be a far larger change.
Co-authored-by: rayim <rayim@fxy.global>
* test(e2e): assert the occlusion invariant, not the runner's cell width
CI covered four columns where this machine covers two — 34.4px over an 8.43px
grid against 12.3px over an 8px grid — so pinning the covered text verbatim
pinned the font metrics rather than the behaviour. Assert instead that every
committed cell the overlay covers appears in what it draws, which is the actual
invariant and holds at any cell width.
Still fails against main: covers "하" / renders "가".
* fix(terminal): keep the rendered tail's spacing on the grid
The composition view is white-space: nowrap, which collapses runs of spaces
exactly like normal — it only suppresses wrapping. So a committed tail carrying
padding drew its trailing glyph cells left of where the grid has them: measured
in Chromium with xterm's own rule, twenty spaces plus a border rendered two
cells wide instead of twenty-one.
The visible case is Orca's most common IME context — composing inside an agent
TUI input box, where the row is a prompt, padding, then a real border glyph the
trim cannot drop. A stray border appeared a cell after the preedit while the
real one stayed put.
xterm sets white-space: pre on its grid rows for this reason; the view was only
nowrap-safe while it held preedit text alone.
The existing fixtures are all space-free, and the e2e invariant is that the
overlay renders everything it covers — collapsing makes it cover less, so both
stayed green. Pinned with a padded-row fixture.
---------
Co-authored-by: rayim <rayim@fxy.global>