Skip to content

Codex/ios cjk input fix#55

Open
fufesou wants to merge 12 commits into
masterfrom
codex/ios-cjk-input-fix
Open

Codex/ios cjk input fix#55
fufesou wants to merge 12 commits into
masterfrom
codex/ios-cjk-input-fix

Conversation

@fufesou

@fufesou fufesou commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Fixes iOS soft keyboard handling for CJK IMEs in remote sessions.

The previous iOS text diff logic treated any active composing range as text that should not be sent. Some iOS IMEs keep a composing range briefly after the user confirms a candidate, so committed Chinese text could be held until another key, such as space, causes another text change.

Changes

  • Extracted iOS soft keyboard diffing into ios_soft_keyboard_input.dart.
  • Tracks pending composing text so pinyin, stroke, bopomofo, Japanese kana, and Korean jamo/hangul composition are not sent prematurely.
  • Sends committed CJK text immediately via sessionInputString, including Japanese kanji/kana and Korean hangul once composition is committed.
  • Uses a text controller listener on iOS so commits where only the composing range changes are still observed.
  • Added focused tests for Chinese, Japanese, Korean, ASCII, and backspace behavior.

Testing

  • iOS soft keyboard basics: ASCII, space, return, backspace
  • iOS CJK IME: Korean, Chinese pinyin/stroke/bopomofo, Japanese
  • iOS accents and emoji smoke tests
  • Android soft keyboard basics: ASCII, space, return, backspace
  • flutter test --no-pub test/ios_soft_keyboard_input_test.dart test/input_modifier_utils_test.dart

Known limitation:

  • handwriting and Korean input can still expose intermediate composing text on the controlled side; this is left as-is for now because holding those events caused worse regressions

Known issues

  • Pressing multiple keys simultaneously, such as "d" and "f", will select the hidden TextFormField input, causing the Sentinel's baseline to shift. Subsequent input may result in the controlled text continuously deleting a large chunk of content, continuously inserting a large number of 1s, or deleting and then inserting.

Summary by CodeRabbit

  • Bug Fixes

    • Improved iOS soft-keyboard handling to avoid duplicate/missed input and better preserve composing state across Chinese, Japanese, and Korean IMEs.
  • Refactor

    • Reworked iOS text-input flow and keyboard-visibility interactions for more reliable composition tracking and cleaner input routing.
  • Tests

    • Added extensive unit tests validating a wide range of iOS IME composition scenarios and edge cases.

CodeEagle and others added 10 commits May 19, 2026 11:59
Signed-off-by: fufesou <linlong1266@gmail.com>
Signed-off-by: fufesou <linlong1266@gmail.com>
Signed-off-by: fufesou <linlong1266@gmail.com>
Signed-off-by: fufesou <linlong1266@gmail.com>
Signed-off-by: fufesou <linlong1266@gmail.com>
Signed-off-by: fufesou <linlong1266@gmail.com>
Signed-off-by: fufesou <linlong1266@gmail.com>
Signed-off-by: fufesou <linlong1266@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f975ab65-3d29-4ae3-9538-4c66ce36b5a3

📥 Commits

Reviewing files that changed from the base of the PR and between 217e4d1 and 1dbc142.

📒 Files selected for processing (1)
  • flutter/lib/mobile/utils/ios_soft_keyboard_input.dart

Walkthrough

Adds a diff-driven iOS IME input pipeline in a new utils module, integrates an iOS-only text-controller listener and state into RemotePage with keyboard-event gating, and adds extensive unit tests for multi-IME composing and sentinel edge cases.

Changes

iOS soft-keyboard input handling

Layer / File(s) Summary
RemotePage iOS listener wiring and keyboard event control
flutter/lib/mobile/pages/remote_page.dart
Wires an iOS-only _textController listener, adds _iosComposingValue/controller tracking, resets tracking on keyboard hide/open, disables TextFormField.onChanged on iOS, gates RawKey forwarding when soft-edit UI shown, and cleans up listener/controller in dispose.
Core diff entry, constants, and Korean normalization
flutter/lib/mobile/utils/ios_soft_keyboard_input.dart
Adds IME constants/lookup tables, public action/result models, and implements diffIOSSoftKeyboardInput orchestration including Korean sentinel normalization and tail baseline logic.
Partial commit strategies and Pinyin/Bopomofo helpers
flutter/lib/mobile/utils/ios_soft_keyboard_input.dart
Implements partial-commit detection and result builders for Pinyin and Bopomofo, deriving held composing suffixes and producing committed-tail actions.
Tail edit actions, composing validation, and classification
flutter/lib/mobile/utils/ios_soft_keyboard_input.dart
Generates rune-aware tail edit actions, validates composing ranges and hold decisions, detects invalid Korean composing/sentinel cases, and classifies composition kinds with script-specific helpers.
Comprehensive unit tests
flutter/test/ios_soft_keyboard_input_test.dart
Adds extensive tests covering diff behavior across Pinyin/Bopomofo/Japanese/Korean IME scenarios, sentinel-prefix edge cases, emoji boundaries, backspace semantics, and a stateful _IOSSoftKeyboardInputSession test helper.

Sequence Diagram

sequenceDiagram
  participant TextCtrl as iOS Text Controller
  participant Listener as _handleIOSTextControllerChanged
  participant Diff as diffIOSSoftKeyboardInput
  participant Handler as _handleIOSSoftKeyboardInput
  participant Input as inputModel

  TextCtrl->>Listener: onChange(text, composingRange, selection)
  Listener->>Diff: diff(previousValue, currentValue, composingRange, previousControllerState)
  Diff-->>Listener: IOSSoftKeyboardInputResult(nextValue, nextComposingValue, actions)
  Listener->>Handler: forward diff result
  Handler->>Input: apply actions (inputKey / inputChar / sessionInputString)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I nibble runes of pinyin, kana, hangul bright,

I patch the keys by moon and morning light,
A diff sings changes, composing held or free,
Actions hop along — inputKey, text, and backspace,
One small rabbit cheer for smoother iOS typing!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is vague and uses non-descriptive terminology that doesn't clearly convey the main change to someone unfamiliar with the codebase. Revise the title to be more descriptive, such as 'Fix iOS soft keyboard handling for CJK IMEs in remote sessions' or 'Extract iOS soft keyboard diffing logic for better CJK composition handling'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/ios-cjk-input-fix

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@flutter/lib/mobile/utils/ios_soft_keyboard_input.dart`:
- Around line 716-727: The recursive function _isPinyinSyllableSequencePrefix
can explode on pathological inputs; add protection by memoizing results (use a
Map<String,bool> keyed by token) or by adding a depth/length cutoff before
recursing to avoid exponential exploration. Update
_isPinyinSyllableSequencePrefix to check the memo cache (and store results)
and/or bail out when token length or recursion depth exceeds a safe threshold;
keep using _isCompletePinyinSyllable and _isPinyinSyllablePrefixWithFinal for
boundary checks so behavior is preserved for normal short IME inputs.

In `@flutter/test/ios_soft_keyboard_input_test.dart`:
- Around line 1316-1341: Extract the hardcoded sentinel prefix length 3 into a
named constant (e.g., const int kSentinelPrefixLength = 3) and use that constant
inside the _IOSSoftKeyboardInputSession.diff method (replace
sentinelPrefixLength: 3) and in the other test sites that currently use the
literal 3; update any related comments to mention the constant so the
relationship to the '111' sentinel prefix is explicit and maintainable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 29b034b4-c1fd-4cdc-84e2-dbc49ebdc601

📥 Commits

Reviewing files that changed from the base of the PR and between 84af60c and 732adeb.

📒 Files selected for processing (3)
  • flutter/lib/mobile/pages/remote_page.dart
  • flutter/lib/mobile/utils/ios_soft_keyboard_input.dart
  • flutter/test/ios_soft_keyboard_input_test.dart

Comment thread flutter/lib/mobile/utils/ios_soft_keyboard_input.dart
Comment thread flutter/test/ios_soft_keyboard_input_test.dart
Signed-off-by: fufesou <linlong1266@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
flutter/lib/mobile/utils/ios_soft_keyboard_input.dart (1)

657-663: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Use explicit size calculation for supplementary character handling.

_commonPrefixLengthByRunes (line 795) uses RuneIterator.currentSize to correctly handle surrogate pairs, but this function relies on String.fromCharCode(rune).length. While this works in practice, using an explicit calculation is clearer and consistent with the approach elsewhere in this file.

♻️ Suggested change for consistency
 Iterable<int> _runeBoundaryOffsets(String value) sync* {
   var offset = 0;
   for (final rune in value.runes) {
-    offset += String.fromCharCode(rune).length;
+    offset += rune <= 0xFFFF ? 1 : 2;
     if (offset < value.length) yield offset;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter/lib/mobile/utils/ios_soft_keyboard_input.dart` around lines 657 -
663, Replace the implicit length calculation using
String.fromCharCode(rune).length in _runeBoundaryOffsets with an explicit
RuneIterator.currentSize-based calculation to correctly handle surrogate pairs
and match _commonPrefixLengthByRunes; iterate with a RuneIterator over value
(calling moveNext()), add iterator.currentSize to offset for each rune, and
yield offset when offset < value.length.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@flutter/lib/mobile/utils/ios_soft_keyboard_input.dart`:
- Around line 657-663: Replace the implicit length calculation using
String.fromCharCode(rune).length in _runeBoundaryOffsets with an explicit
RuneIterator.currentSize-based calculation to correctly handle surrogate pairs
and match _commonPrefixLengthByRunes; iterate with a RuneIterator over value
(calling moveNext()), add iterator.currentSize to offset for each rune, and
yield offset when offset < value.length.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: eb61f699-0d0d-4395-80c0-bfc9002e653d

📥 Commits

Reviewing files that changed from the base of the PR and between 732adeb and 217e4d1.

📒 Files selected for processing (3)
  • flutter/lib/mobile/pages/remote_page.dart
  • flutter/lib/mobile/utils/ios_soft_keyboard_input.dart
  • flutter/test/ios_soft_keyboard_input_test.dart
💤 Files with no reviewable changes (2)
  • flutter/test/ios_soft_keyboard_input_test.dart
  • flutter/lib/mobile/pages/remote_page.dart

@fufesou

fufesou commented Jun 10, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Signed-off-by: fufesou <linlong1266@gmail.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.

2 participants