fix(input): route IME text as a key event, keep clipboard clean on paste#90
Open
KoalaHao wants to merge 1 commit into
Open
fix(input): route IME text as a key event, keep clipboard clean on paste#90KoalaHao wants to merge 1 commit into
KoalaHao wants to merge 1 commit into
Conversation
Two related fixes for the IME (Chinese / Japanese / Korean input
method editor) text path on macOS and Linux.
On macOS, the Chinese / Japanese / Korean IMEs wrap their
committed candidate text in bracketed-paste markers
(ESC[200~ ... ESC[201~), so a confirmed word like 'tool' or
'你好' arrives at the framework as a PasteInputEvent. Previously
TerminalBinding called ClipboardManager.copy on the payload and
then routed a synthetic Ctrl+V so TextField._paste could read
it back from the clipboard. This worked for real paste, but on
the IME path it overwrote whatever the user had on their
system clipboard every single time they confirmed a candidate.
The framework now stashes the paste text on the binding
(NoctermBinding.setPendingPasteText) and TextField._paste
consumes it via NoctermBinding.instance.consumePendingPasteText
at the top of its paste path. Real user-initiated Ctrl+V has
no pending text and still reads from the system clipboard as
before. The system clipboard is no longer touched on the IME
path.
When the terminal supports the Kitty keyboard protocol's
'report associated text' flag (iTerm2, WezTerm, Ghostty, Kitty),
IME commits now arrive as ordinary key events with the IME's
output in the character field, instead of being routed
through bracketed-paste mode at all. Format:
CSI codepoint ; modifiers ; base_layout ; text u
where text is percent-encoded UTF-8 (so '你好' arrives as
'%E4%BD%A0%E5%A5%BD'). The framework now requests this flag
at startup (ESC[>17u — bit 0 disambiguate + bit 4 associated
text) and the input parser recognizes the 4-part form. The
affected _parseKittySequence uses a backward-then-validate
scan to disambiguate the terminator 'u' from 'u' bytes that
may appear un-percent-encoded inside the associated text
('u' is unreserved in URL encoding, so e.g. 'menu' is sent
verbatim, not as 'men%75').
The benefit on supporting terminals is twofold: (a) IME text
reaches the focused widget as a regular KeyboardInputEvent,
not a PasteInputEvent, so it is no longer misrouted through
any app-level paste handling (e.g. file-drop detectors);
(b) the system clipboard is never touched on the IME path
(even the binding-level stash is bypassed). On terminals
without this flag, IME commits still fall through to the
bracketed-paste path and benefit from fix #1.
A small refactor extracts _logicalKeyForCodepoint from
_codepointToKeyEvent so the codepoint → LogicalKey mapping
is shared between the 1-3 part form (character derived from
codepoint) and the 4-part form (character from associated
text, logical key from codepoint).
- 4 new tests in text_field_clipboard_test.dart cover the
pending-paste-text mechanism (IME path, fallthrough to
clipboard, real Ctrl+V, no-leak to subsequent Ctrl+V).
- 14 new tests in kitty_associated_text_test.dart cover the
4-part form (ASCII text, CJK text, percent-encoding,
sub-parameters, codepoint driving logicalKey, two
sequences back-to-back, 5-part rejection, mixed
bracketed-paste + kitty stream, 2-/3-part regression).
KoalaHao
force-pushed
the
fix/ime-kitty-associated-text
branch
from
June 24, 2026 03:37
099bdf2 to
4fa5a11
Compare
KoalaHao
marked this pull request as draft
June 25, 2026 02:06
KoalaHao
added a commit
to marsup-space/nocterm
that referenced
this pull request
Jun 25, 2026
PR Norbert515#90 (kitty 4-part form for IME associated text) changed the kitty sequence parser to scan backward for the 'u' terminator so the parser can correctly handle 4-part sequences whose associated text contains raw 'u' bytes. Add regression tests to lock in the 2-/3-part forms (codepoint + modifier) that macOS Cmd+A/C/V/X rely on — iTerm2 / WezTerm / Ghostty with the kitty keyboard protocol emit these sequences on every Cmd+A/C/V/X press, and the new parser must still surface them with meta=true and the right logicalKey.
KoalaHao
marked this pull request as ready for review
June 25, 2026 06:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two related fixes for the IME (Chinese / Japanese / Korean input
method editor) text path on macOS and Linux.
1. Paste path no longer pollutes the system clipboard
On macOS, the Chinese / Japanese / Korean IMEs wrap their
committed candidate text in bracketed-paste markers
(
ESC[200~ ... ESC[201~), so a confirmed word liketoolor你好arrives at the framework as aPasteInputEvent.Previously
TerminalBindingcalledClipboardManager.copyonthe payload and then routed a synthetic Ctrl+V so
TextField._pastecould read it back from the clipboard. Thisworked for real paste, but on the IME path it overwrote
whatever the user had on their system clipboard every single
time they confirmed a candidate.
The framework now stashes the paste text on the binding
(
NoctermBinding.setPendingPasteText) andTextField._pasteconsumes it via
NoctermBinding.instance.consumePendingPasteTextat the topof its paste path. Real user-initiated Ctrl+V has no pending
text and still reads from the system clipboard as before. The
system clipboard is no longer touched on the IME path.
2. Kitty keyboard protocol: 4-part form (associated text)
When the terminal supports the Kitty keyboard protocol's "report
associated text" flag (iTerm2, WezTerm, Ghostty, Kitty), IME
commits now arrive as ordinary key events with the IME's output
in the
characterfield, instead of being routed throughbracketed-paste mode at all. Format:
where
textis percent-encoded UTF-8 (so你好arrives as%E4%BD%A0%E5%A5%BD). The framework now requests this flag atstartup (
ESC[>17u— bit 0 disambiguate + bit 4 associatedtext) and the input parser recognizes the 4-part form. The
affected
_parseKittySequenceuses a backward-then-validate scanto disambiguate the terminator
ufromubytes that mayappear un-percent-encoded inside the associated text (
uisunreserved in URL encoding, so e.g.
menuis sent verbatim,not as
men%75).The benefit on supporting terminals is twofold: (a) IME text
reaches the focused widget as a regular
KeyboardInputEvent,not a
PasteInputEvent, so it is no longer misrouted throughany app-level paste handling (e.g. file-drop detectors); (b)
the system clipboard is never touched on the IME path (even
the binding-level stash is bypassed). On terminals without
this flag, IME commits still fall through to the bracketed-paste
path and benefit from fix #1.
A small refactor extracts
_logicalKeyForCodepointfrom_codepointToKeyEventso the codepoint → LogicalKey mappingis shared between the 1–3 part form (character derived from
codepoint) and the 4-part form (character from associated
text, logical key from codepoint).
Tests
text_field_clipboard_test.dartcover thepending-paste-text mechanism (IME path, fallthrough to
clipboard, real Ctrl+V, no-leak to subsequent Ctrl+V).
kitty_associated_text_test.dartcover the4-part form (ASCII text, CJK text, percent-encoding,
sub-parameters, codepoint driving
logicalKey, twosequences back-to-back, 5-part rejection, mixed
bracketed-paste + kitty stream, 2-/3-part regression).