Skip to content

rioterm: render the IME composition inline at the cursor - #1849

Merged
raphamorim merged 16 commits into
mainfrom
preedit-v2
Sep 6, 2026
Merged

raphamorim merged 16 commits into
mainfrom
preedit-v2

Conversation

@raphamorim

@raphamorim raphamorim commented Aug 10, 2026 •

Copy link
Copy Markdown
Owner

From-scratch replacement for the approach in #1557/#1846 (closed), designed around everything the deep review of those branches surfaced. rioterm currently renders no composition preview at all — a dead key ' or Japanese romaji shows nothing until commit.

Design

  • Single row, slide-left, tail-biased. The composition occupies exactly the cursor row. It slides left at the right edge, and when wider than the whole row it drops leading clusters so the tail — where the IME caret lives — stays visible. It never wraps to the next line and never silently truncates (the fix(ime): render preedit inline and stop Windows key-repeat from stalling redraws #1846 model wrapped downward and composed blind on the bottom row, the most common prompt position).
  • Grapheme clusters, not chars. Layout and shaping are per cluster: ZWJ emoji and combining marks hold together as one cell-slot and one shaped run (clamped to the 1-or-2 cells a terminal can express).
  • Contrast-safe caret. On a composition cell the caret is a thick underline in the text color (ghostty underlines composition cells too, but has no moving segment caret; the segment navigation is ours) — the old beam was cursor-colored on a cursor-colored block, invisible exactly where arrow-key segment navigation needs it. Past the end of the text it's a beam in the cursor color against the normal background. A caret the IME reports as hidden (Wayland cursor_begin = -1, Windows, while paging through candidates) draws nothing, honoring rio-window's Preedit contract.
  • Window-level IME state. Ime moved from Context to Screen: the platform IME composes into the focused view — one composition per window — so per-context state could go stale on a background split, keep painting a frozen block, and swallow Enter/Ctrl-C on refocus through the preedit key gate. Window-level state makes the whole class unrepresentable.
  • Lifecycle correctness. Never rendered over scrollback (the anchor row is off-viewport). The composition row and the row it just left are force-rebuilt each frame while composing, so cursor-only damage or a cancelled composition can't leave stale cells; Ime::Disabled mid-composition (input-source switch) damages and repaints like the Preedit arm.
  • Color coherence. The block fill resolves the cursor color exactly as the cursor-block uniforms do (OSC 12 wins, then theme), so the forced block cursor and the composition can't be two different colors. Composition runs live in their own run-cache namespace — no aliasing with grid glyphs to reason about.

Also included (deliberately coupled): the Windows WM_IME_SETCONTEXT fix — the fork masked ISC_SHOWUICOMPOSITIONWINDOW off wParam, but the flag lives in lParam (upstream winit fixed this), so the OS floating composition window was never actually suppressed. With inline rendering in place, suppressing it is now correct; shipping the fix alone would have removed Windows' only visible composition UI.

Testing

cargo test -p rioterm — 183 passing, including 10 new layout tests (wide clusters, ZWJ emoji, slide-left, tail cropping, caret snapping, degenerate input). Clippy and fmt clean.

Manual checklist for real IMEs:

  • US-Intl ' + e → inverted ' preview at the cursor, then é commits
  • Japanese romaji: composition preview inline, conversion with ←/→ shows the underline caret moving between segments, Enter commits
  • Compose at the last column and on the bottom row of the window
  • Scroll back mid-composition (overlay must vanish, reappear at bottom)
  • Switch splits/tabs mid-composition (no frozen block, keys work on return)
  • Windows: Microsoft IME — no floating OS composition window, candidates positioned at the caret

ISC_SHOWUICOMPOSITIONWINDOW lives in lparam; masking wparam meant the
OS floating composition window was never actually suppressed. Same fix
as upstream winit. Only correct to ship together with inline preedit
rendering: without it this would remove the only visible composition
UI on Windows.
Single-row wezterm-style block anchored at the cursor: slides left at
the right edge, keeps the tail (where the caret lives) when wider than
the row, and never wraps or silently truncates. Text is laid out and
shaped per grapheme cluster so ZWJ emoji and combining marks hold
together. The caret renders as a thick underline in the text color on
composition cells (a beam there would be cursor-on-cursor, invisible)
and as a beam past the end where the normal background is behind it.

Ime state moves from Context to Screen: the platform IME composes into
the focused view, one composition per window, so per-context state
could go stale on a background split — keep painting, and swallow keys
on refocus through the preedit gate. Window-level state makes that
unrepresentable. The overlay is also never built over scrollback,
where the cursor row is off-viewport and the anchor would lie.

The composition row (and the row it just left) is force-rebuilt every
frame while composing, so cursor-only damage or an ended composition
can't leave stale block cells. Composition runs use their own run-cache
namespace, and the block fill resolves the cursor color the same way
the cursor-block uniforms do, OSC 12 included.
torabit added a commit to torabit/dotfiles that referenced this pull request Aug 24, 2026
rio 0.5.26 には IME の未確定文字列を描画するコードが無い。rio-window は
Windows から composition string を取得して Ime::Preedit を送出し rioterm も
それを保持するが、描画側は先頭 1 文字を renderable_content.cursor.content へ
代入するだけで、そのフィールドはどこからも読まれない。一方 rio-window は
WM_IME_COMPOSITION で DefWindowProc を呼ばず OS 側の描画を抑止している。
結果、自前でも OS でも描かれず、確定するまで何も見えない。

メンテナ自身の実装 PR raphamorim/rio#1849 が CI 全 green のまま未マージで
main と衝突している。それを v0.5.26 へ移植したブランチを torabit/rio の
torabit/fix/ime-preedit-inline に置き、マージされるまで自前でビルドする。

msvc ではなく windows-gnu を狙うのは、Windows 側に Visual Studio Build Tools
を入れず WSL の mingw-w64 だけで済ませるため。rio の CI も MSYS2 で通っている。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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