Skip to content

Fix dead keys not working in the editor on desktop#17

Merged
Wavesonics merged 2 commits into
mainfrom
fix/561-desktop-dead-keys
Jun 7, 2026
Merged

Fix dead keys not working in the editor on desktop#17
Wavesonics merged 2 commits into
mainfrom
fix/561-desktop-dead-keys

Conversation

@Wavesonics

Copy link
Copy Markdown
Owner

Fixes the editor dropping dead-key / accent composition on desktop (Linux): reported in Wavesonics/hammer-editor#561, where ´+a produced a instead of á in the main editor, even though dead keys worked in every other field.

Root cause

The desktop TextEditorTextInputService never opened a platform input-method session — it just called awaitCancellation(). Without an AWT input-method context attached to the focused editor, Linux/XIM has nowhere to deliver composed text, so only the base letter arrives via KEY_TYPED. Standard BasicTextFields elsewhere work because they do open the session.

Fix

  • Implement a real PlatformTextInputMethodRequest on desktop via startInputMethod: a live TextEditorState adapter, an editText scope, and focusedRectInRoot for candidate-window placement. Compose's DesktopTextInputService2 then attaches InputMethodRequests and routes InputMethodEvents into editText. Plain KEY_TYPED input is unchanged and coexists exactly as in Compose's own BasicTextField.
  • Extract the IME edit operations shared by Android and desktop into a new commonMain ImeEditLogic, and refactor the Android InputConnection to use it — composing/cursor/surrogate semantics stay identical across platforms.
  • Capture the editor canvas layout coordinates so the desktop IME can place the composition window at the cursor (CJK).

Side benefit: the same path should also enable macOS Option-key composition and the Windows emoji picker in the editor (the previous "known limitations" in the desktop input service); only Linux dead keys were tested here.

Tooling

Wires compose-hot-reload v1.2.0-alpha01 into :sampleApp for hot-reload + MCP-driven verification of the running desktop app. (Local .mcp.json is gitignored.)

Verification

  • :ComposeTextEditor:desktopTest — 429 tests pass, including 8 new ImeEditLogicTest cases covering commit / dead-key compose-then-commit / CJK / selection-replace / surrogate-pair delete / cursor-position contract.
  • Android target compiles (Android IME refactor is behavior-preserving).
  • Live keypress test on Linux (us+intl layout): ñ, á, è, ü committed as single composed characters; hello types with no double-insertion.

The desktop editor never opened a platform input-method session — the
desktop TextEditorTextInputService just called awaitCancellation(). Without
an AWT input-method context, Linux/XIM had nowhere to deliver composed
dead-key / accent text, so the editor only received the base letter (while
standard BasicTextFields elsewhere worked because they open the session).

Implement a real PlatformTextInputMethodRequest on desktop via
startInputMethod: a live TextEditorState adapter, an editText scope, and
focusedRectInRoot for IME candidate-window placement. Compose's
DesktopTextInputService2 then attaches AWT InputMethodRequests and routes
InputMethodEvents (dead keys, CJK, macOS press-and-hold, Windows emoji
picker) into editText. Plain KEY_TYPED input is unchanged and coexists as
it does in Compose's own BasicTextField.

Extract the IME edit operations shared by Android and desktop into a new
commonMain ImeEditLogic, and refactor the Android InputConnection to use
it so composing/cursor/surrogate semantics stay identical across platforms.
Capture the editor canvas layout coordinates so the desktop IME can place
the composition window at the cursor.

Also wire compose-hot-reload v1.2.0-alpha01 into :sampleApp for hot-reload
+ MCP-driven verification of the running desktop app.

Verified: 429 desktop tests pass (incl. 8 new IME-logic tests covering the
dead-key/accent/CJK/surrogate cases); live keypress test on Linux shows
ñ/á/è/ü committed as single composed characters with no double-insertion.
- Extract the selection/composition/text mapping used by the desktop and iOS
  PlatformTextInputMethodRequests into commonMain helpers (selectionAsTextRange,
  composingAsTextRange, imeSubSequence, imeCharAt). Both platforms' adapters now
  delegate to one implementation instead of duplicating it. (The adapter class
  itself stays per-platform: the Compose TextEditorState interface is skiko-only
  and not in the common API surface.)
- IME char/substring queries now read only the requested range via getStringInRange
  instead of rebuilding the entire document with getAllText() on every call —
  avoids O(n)-per-access allocation during composition on large documents.
- imeCharAt enforces the CharSequence out-of-range contract explicitly.
- Fix a misleading comment in ImeEditLogic: the platform "batch" suppresses IME
  cursor-sync notifications, it does not coalesce undo history.

All three targets (desktop, android, iosSimulatorArm64) compile; 429 desktop
tests pass.
@Wavesonics Wavesonics merged commit 567b51c into main Jun 7, 2026
1 check 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