Skip to content

Added: IME composing text preview with in-buffer pre-edit cursor (CJK) - #5242

Open
sng2c wants to merge 5 commits into
termux:masterfrom
sng2c:master
Open

Added: IME composing text preview with in-buffer pre-edit cursor (CJK)#5242
sng2c wants to merge 5 commits into
termux:masterfrom
sng2c:master

Conversation

@sng2c

@sng2c sng2c commented Aug 6, 2026

Copy link
Copy Markdown

Problem

Termux renders only text committed and echoed back from the pty, so in-progress
IME composition (Hangul/Pinyin/Kana) is invisible until commit. The previous
attempt (#5215) forced a commit on every non-modifier onKeyDown — including
arrow keys — so the IME could not move the cursor within its own composing
buffer, making the pre-edit cursor unusable ([ghostty-org/ghostty#11837]). It
was withdrawn to be reworked.

Solution

An implementation of the interaction with the IME, not of any language.
Android IMEs deliver composing text over a span on the InputConnection's
Editable and drive a pre-edit cursor through Selection. The preview is
driven by exactly that spannable state — composing text + cursor position
within it — so it faithfully reflects the IME rather than inferring from key
events. No script-specific logic; works for any composing IME following the
standard InputConnection contract.

How it works

  • setComposingText() → capture + render as overlay at cursor
    (TerminalRenderer.renderComposingText()), reusing mTextPaint/drawTextRun.
    Underlined to mark pre-edit.
  • Pre-edit cursor from Selection within composing span → cursor movement
    inside composing buffer visible (GNOME-Terminal-like).
  • setSelection() redraws on in-buffer move; finalizes only when cursor leaves
    composing region.
  • commitText() / finishComposingText() → pty via standard commit path.
  • onKeyDown() finalizes only for IME-bypassing keys; modifier keys excluded
    (KeyEvent.isModifierKey) — shift+ㅅ→ㅆ no longer aborts composition. Enables
    CJK IME composing on both software and hardware keyboards (Bluetooth/USB);
    previously (TYPE_NULL) hardware keyboards could not compose through the IME
    at all.

Opt-in, no regression

Settings → Terminal I/O → "IME Composing Preview" (ime_composing_enabled,
default false):

settings_now

Off = TYPE_NULL unchanged. Returning from Settings recreates
InputConnection → applies without termux-reload-settings.

CJK demos

Korean Japanese Chinese
cjkv_ko cjkv_ja cjkv_zh

Vietnamese — future work

Gboard Vietnamese (Telex) uses commitText-per-keystroke + replaceText
(in-place replace, not composing). A terminal's display is the pty echo, not
an EditablereplaceText can't backspace+rewrite the echoed glyph.
Fixing requires reworking InputConnection to a real-Editable with delta
sync (like SwiftTerm-iOS UITextInput). Out of scope; tracked as future work.

Changed files

  • TerminalView.javaInputConnection overrides, pre-edit cursor, modifier guard
  • TerminalRenderer.javarenderComposingText() overlay
  • TerminalViewClient.javashouldEnableImeComposing() callback
  • termux-shared + app — wiring + Settings toggle (ime_composing_enabled)

Reworks #5215.

sng2c added 5 commits July 28, 2026 22:56
…n-composing keys

The terminal only renders text that is committed and echoed back by the
program in the pty, so in-progress IME composition (e.g. ㄱ → 가 → 간) was
not visible until commit, and the preview followed the cursor when a
non-composing key (arrows/ESC/Tab from extra-keys or hardware keyboard)
was pressed mid-composition.

- Override InputConnection.setComposingText() to capture the composing
  text and draw it as a preview overlay at the cursor via a new
  TerminalRenderer.renderComposingText(), reusing the same mTextPaint
  and drawTextRun path as normal rendering (same font family/fallback,
  width scaling and cell text style: bold/italic/colors/dim).
- Finalize composing on non-modifier keys in onKeyDown(): write the
  composing code points to the pty directly, clear the local Editable,
  and reset the IME via invalidateInput (API 33+) / restartInput so it
  drops its composing buffer (avoids duplication on the next input).
…etting

Show in-progress IME composing text (e.g. Hangul ㄱ→가→간) as an inline
preview at the terminal cursor, and commit it through the InputConnection
path (no direct pty write). The terminal only renders committed text, so
without this the composing characters are invisible until commit.

Opt-in via `enable-ime-composing` (default false; TYPE_NULL behavior
unchanged = no regression). When enabled:
- Use TYPE_TEXT_VARIATION_NORMAL|NO_SUGGESTIONS so the IME composes at
  word level (VISIBLE_PASSWORD is fatal for CJKV — it disables composing).
- Store the BaseInputConnection and finalize in-progress composition via
  finishComposingText() when input bypasses the IME (extra-keys literals /
  hardware keys via onKeyDown/inputCodePoint). If the fake Editable is
  empty, copy the composing text into it so the commit is not lost.
- Guard finishComposingText with `hadComposing` so restartInput's
  closeConnection does not double-commit.
- restartInput after finalize so the IME discards its stale composing
  buffer; only after finalize (not every commit) so digit input is
  unaffected.
- Render the IME pre-edit cursor (Selection within the composing span).

Samsung keyboard is not supported (non-standard composing buffer: does not
clear after commit, causing duplicates that cannot be fixed without breaking
digit input). Gboard (AOSP LatinIME-based) works.
…toggle, CJKV wording

- Do not finalize IME composing on modifier key events (shift/ctrl/alt/meta/
  sym/fn) in onKeyDown. Pressing shift to build a CJKV double consonant
  (e.g. Korean ㅆ = shift+ㅅ for 했) previously aborted the in-progress
  composition (해): finalize committed it and the posted restartInput cleared
  the IME's composing buffer, so the following ㅅ entered detached. Guard with
  KeyEvent.isModifierKey(keyCode); non-modifier keys still finalize, so
  hardware-key/extra-keys behavior is unchanged. Digit input and committed
  text unaffected (verified on-device with a standard-compliant keyboard).

- Move the enable flag from termux.properties (`enable-ime-composing`) to a
  SharedPreferences toggle in Settings -> Terminal I/O -> "IME Composing
  Preview" (`ime_composing_enabled`, default false). Returning from the
  Settings activity recreates the InputConnection, so the change applies
  without `termux-reload-settings`. Follows the existing convention that
  termux.properties and SharedPreferences keys are disjoint.

- Describe the feature as CJKV and "standard-compliant keyboards" instead of
  naming specific IMEs.
# Conflicts:
#	terminal-view/src/main/java/com/termux/view/TerminalRenderer.java
#	terminal-view/src/main/java/com/termux/view/TerminalView.java
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