support CJK input methods by implementing UITextInput marked text - #4
support CJK input methods by implementing UITextInput marked text#4sinsoul wants to merge 1 commit into
Conversation
The UITextInput shim previously stubbed all marked-text methods as no-ops, which prevented CJK input methods (Pinyin, Wubi, etc.) from establishing a composing region. Without a valid composing region the system IME cannot display candidates or commit selected characters. This change: - Tracks composing state in storedMarkedText / storedMarkedSelectedRange so markedTextRange reports the active composing region to UIKit. - Implements setMarkedText to store the composing string and notify the input delegate, allowing the candidate bar to appear. - Implements unmarkText to commit the composed text to the terminal, covering the setMarkedText-then-unmarkText commit path used by the system Pinyin keyboard. - Clears composing state in insertText, deleteBackward, and replace so all IME commit and cancel paths behave correctly. - Adjusts endOfDocument, text(in:), selectedTextRange, and position(from:offset:) to reflect the dynamic virtual document length while composing. - Returns a small rect at the bottom of the view from firstRect/caretRect so the system can anchor the candidate window on screen.
|
hey @sinsoul, will take a look this and support for CJK input methods. |
|
@coderabbitai full review |
✅ Action performedFull review finished. Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 19 minutes. |
|
Warning Review limit reached
Next review available in: 19 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 46df2fce06
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let loc = storedMarkedSelectedRange.location == NSNotFound | ||
| ? marked.count | ||
| : min(storedMarkedSelectedRange.location, marked.count) | ||
| let pos = GhosttyVirtualTextPosition(offset: loc) | ||
| return GhosttyVirtualTextRange(from: pos, to: pos) |
There was a problem hiding this comment.
Preserve the marked selection length
When an IME supplies a nonempty selectedRange—for example, while selecting a conversion segment in a Japanese or Chinese composition—the getter collapses it to [location, location] and discards storedMarkedSelectedRange.length. UIKit therefore observes no selected segment, which can disrupt clause conversion and candidate replacement; construct the range end from the supplied length, clamped to the marked document.
Useful? React with 👍 / 👎.
| private var documentLength: Int { | ||
| storedMarkedText?.count ?? 0 | ||
| } |
There was a problem hiding this comment.
Use UTF-16 offsets for the virtual document
When marked text contains a supplementary or composed character, NSRange offsets supplied by UIKit are based on the marked NSString's UTF-16 representation, while String.count counts extended grapheme clusters. For example, a caret after 𠮷 arrives at offset 2 but this document reports length 1 and clamps it there, making selection and text ranges inconsistent; use UTF-16 length and UTF-16-aware slicing throughout the virtual position implementation.
Useful? React with 👍 / 👎.
| if storedMarkedText != nil { | ||
| inputDelegate?.textWillChange(self) | ||
| storedMarkedText = nil | ||
| storedMarkedSelectedRange = NSRange(location: NSNotFound, length: 0) | ||
| inputDelegate?.textDidChange(self) | ||
| return |
There was a problem hiding this comment.
Delete only one unit from active marked text
When a keyboard routes Backspace through UIKeyInput.deleteBackward() during a multi-character composition, this branch clears the entire marked buffer and emits neither an updated marked value nor a terminal backspace. A single delete therefore cancels all preedit text instead of deleting the selected or preceding character; update the marked buffer and selection, clearing it only once it becomes empty.
Useful? React with 👍 / 👎.
|
@h3nock can we review this, current there is no way to input Chinese or like. |
sorry about that. will be included in the next build. |
Summary
UITextInputmarked text (composing) support so CJK input methods(Pinyin, Wubi, etc.) can establish a composing region, display candidates,
and commit selected characters to the terminal.
system IME to silently discard composing state — no candidate bar appeared
and no CJK text could be entered via the software keyboard.
What changed
GhosttyTerminalResponderView.swiftstoredMarkedTextandstoredMarkedSelectedRangeproperties to trackthe active composing state across the
UITextInputextension in the shim.insertTextnow clears any active composing state before forwarding thecommitted text to the terminal.
deleteBackwardclears composing state (instead of sending a backspace keyevent) when an IME composing region is active.
GhosttyTerminalResponderTextInputShim.swiftsetMarkedTextstores the composing string and notifiesinputDelegate,enabling the system candidate bar.
unmarkTextcommits the stored composing text to the terminal viasubmitTextInput, then clears the composing state. This covers thesetMarkedText → unmarkTextcommit path used by the system Pinyin keyboard.markedTextRangereturns the active composing region (previously alwaysnil).text(in:)returns the composing string content for the marked range.selectedTextRangeandendOfDocumentreflect the dynamic virtual documentlength while composing.
replace(_:withText:)clears composing state before forwarding replacementtext.
firstRect(for:)andcaretRect(for:)return a small rect at the bottom ofthe view so the system can anchor the candidate window on screen (previously
returned
.zero).GhosttyTerminalResponderViewTests.swiftunmark-commits-to-terminal, insert-clears-composing, delete-during-composing,
replace-during-composing, nil-marked-range baseline, and dynamic document
length.
Test plan
candidate bar appears, selecting a candidate commits the character to the
terminal