Skip to content

fix(ime): preserve Korean IME last syllable across commits (#8919)#9713

Open
ohah wants to merge 2 commits intowarpdotdev:masterfrom
ohah:ohah/fix-issue-8919-korean-ime-commit
Open

fix(ime): preserve Korean IME last syllable across commits (#8919)#9713
ohah wants to merge 2 commits intowarpdotdev:masterfrom
ohah:ohah/fix-issue-8919-korean-ime-commit

Conversation

@ohah
Copy link
Copy Markdown

@ohah ohah commented May 1, 2026

Description

Fixes a long-standing Korean (and broader CJK) IME bug on macOS where the last composing character could be silently dropped — both when terminating composition with Enter/Arrow keys and during ongoing composition that bridges syllables.

Linked Issue

Fixes #8919.

  • The linked issue is labeled triaged (implicitly ready-to-implement per CONTRIBUTING.md).
  • Screenshots / video

Root cause

Two bugs in crates/warpui/src/platform/mac/objc/host_view.m's keyDownImpl: interacted with the way Korean (and other CJK) IMEs commit pre-edit text:

  1. !handled guard discarded committed text on Arrow/Enter.
    When the user pressed Enter/Arrow to terminate composition, the macOS Korean IME committed the trailing syllable via insertText:, but the existing guard if ([textToInsert length] > 0 && !handled) then discarded it because the input field had handled the key. So the very last syllable (e.g. the '요' in '안녕하세요') was lost.
  2. Commit overwrote the next marked-text selection.
    Korean IMEs can both commit a previous syllable AND start a new composition within the same keyDown — typing 'ㅏ' after '간' produces commit '가' + new marked '나'. By the time we dispatched the commit, setMarkedText: had already placed the new marked text as a selection in the input field's buffer, and insert_internal (called by user_insert) overwrote that selection with the committed text — losing the new syllable.

Fix

In keyDownImpl::

  1. Loosen the guard to (!handled || wasComposing) so a key that was both consumed by the input field AND committed previously-composed text still dispatches the committed text (it represents the user's already-typed input, not text produced by the keybinding).
  2. When dispatching a commit while a new marked-text composition is already in place, temporarily clear marked text, dispatch the commit, then re-apply the marked text. The new selectedRange is captured at setMarkedText: time into a new instance variable lastMarkedSelectedRange so we can re-emit the same range afterwards.

Testing

Manual repro on macOS with cargo run --features ime_marked_text (release/dogfood builds enable this flag automatically — see crates/warp_features/src/lib.rs):

  1. Switch to macOS Korean IME.
  2. Type 안녕하세요 and press Enter → before: 안녕하세 is sent, lost. After: full 안녕하세요 is sent.
  3. Type 안녕하세요 and press → / ↓ → before: trailing dropped. After: preserved.
  4. Type 가나다 slowly and watch composition during typing → before: the last syllable being composed (e.g. ) wasn't shown until the next syllable bumped it. After: each composing syllable is visible as marked text in real time.
  5. Sanity check: dead-key flow (Option-E + g → ´g) still works; English input unaffected.

No automated tests added — host_view.m is Objective-C and not directly unit-testable; the marked_text_tests.rs suite exercises the editor model layer below this fix and continues to pass. Happy to add an integration test if reviewers want one.

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode
now.mov
after.mov

Korean (and other CJK) IMEs on macOS can both commit a previous syllable
AND start a new composition within a single keyDown — e.g. typing 'ㅏ'
after '간' produces commit '가' plus new marked '나', and pressing
Enter/Arrow on '안녕하세요' commits the trailing '요'. Two bugs in
host_view.m caused the last syllable to be lost in these flows:

1. The `!handled` guard around the textToInsert dispatch dropped the
   committed text whenever the key was handled by the input field
   (Arrow/Enter etc.), so the last Korean syllable was discarded.
   Fixed by also dispatching the commit when `wasComposing` was true on
   entry — the text represents the user's already-typed input, not text
   produced by the keybinding.

2. By the time we dispatch the commit, setMarkedText: may have already
   placed the next composition as a selection in the input field's
   buffer. Inserting the committed text on top of that selection would
   overwrite it (losing the next character). Worked around by
   temporarily clearing the marked text, dispatching the commit, and
   re-applying the marked text afterwards.

Fixes warpdotdev#8919
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 1, 2026

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have the users @ohah on file. In order for us to review and merge your code, each contributor must visit https://cla.warp.dev to read and agree to our CLA. Once you have done so, please comment @cla-bot check to trigger another check.

@github-actions github-actions Bot added the external-contributor Indicates that a PR has been opened by someone outside the Warp team. label May 1, 2026
@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 1, 2026

@ohah

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and posted feedback on this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

oz-for-oss[bot]
oz-for-oss Bot previously requested changes May 1, 2026
Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This PR adjusts macOS IME handling so committed text from an active composition can still be dispatched after handled key events, and attempts to preserve newly-started marked text across that commit.

Concerns

  • The new restoration path treats any remaining markedText as newly-started marked text, but insertText: deliberately leaves the previous marked text in place while interpretingKeyEvents is true. Commit-only flows such as Enter/dead-key completion can therefore re-emit stale marked text instead of ending composition.

Verdict

Found: 0 critical, 1 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

// text would then overwrite that selection (losing the next
// character). Workaround: temporarily clear the marked text,
// dispatch the commit, then re-apply the marked text.
BOOL hasNewMarked = [self hasMarkedText];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] hasNewMarked is also true for the already-committed composition because insertText: intentionally skips unmarkText while interpretingKeyEvents is true, so commit-only flows will clear and then re-emit stale marked text instead of ending IME state; distinguish newly-started marked text from stale markedText before taking this branch.

…tore unmarkText after commit-only flows

Address @oz-for-oss feedback on warpdotdev#9713: the previous version of this fix
treated any non-empty markedText after interpretKeyEvents: as a
freshly-started composition, but insertText: deliberately skips
unmarkText while interpretingKeyEvents is true. So a commit-only flow
(e.g. dead-key + char) leaves the previous markedText in place, and the
fix would re-emit it as if it were new — and, worse, by skipping
unmarkText on that branch the host_view's IME state stayed "active",
breaking the next keystroke (e.g. Backspace did nothing because the
input field was still in composing state).

Two changes:

1. Track whether setMarkedText: was actually called during the current
   interpretKeyEvents: via a new BOOL `setMarkedTextDuringInterpret`,
   and only take the clear-commit-restore path when it is true. This
   distinguishes "IME just started a new composition mid-keyDown" from
   "IME committed but left stale markedText behind".

2. On the commit-only branch (hasNewMarked == false), explicitly call
   `unmarkText` so the stale markedText object and the IME-active flag
   are cleared — matching the original code's invariant that committing
   ends the composition unless a new one was started.
@cla-bot cla-bot Bot added the cla-signed label May 1, 2026
@ohah
Copy link
Copy Markdown
Author

ohah commented May 1, 2026

/oz-review

@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 1, 2026

@ohah

I'm re-reviewing this pull request in response to a review request.

You can view the conversation on Warp.

I reviewed this pull request and requested human review from: @vorporeal.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot dismissed their stale review May 1, 2026 07:50

Oz no longer requests changes for this pull request after the latest automated review.

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This PR updates the macOS host view IME flow so committed Korean/CJK composition text is still dispatched when the terminating key is handled, and preserves newly marked text that starts during the same key event.

Concerns

  • No blocking correctness concerns found in the changed lines.
  • Security pass: no security-sensitive data flow, permission, injection, or trust-boundary changes were introduced by this patch.

Verdict

Found: 0 critical, 0 important, 0 suggestions

Approve

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot requested a review from vorporeal May 1, 2026 07:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed external-contributor Indicates that a PR has been opened by someone outside the Warp team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Korean IME: Last composing character is dropped when pressing Enter

1 participant