mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-24 05:40:15 +00:00
macos: avoid replaying keys that commit preedit (#12547)
Refs #10460 Related: #12518 When an input method commits all or part of marked text during keyDown, AppKit returns the committed text through insertText. Treat that as text committed by the input method instead of replaying the original key event to the terminal. Previously this path only handled arrow-key commits specially. A control-key shortcut that commits preedit text could still be encoded as the original control input after composition, such as ctrl+j becoming LF. Send committed preedit text as a text-only event for any key that causes the commit. Only replay arrow navigation keys that the existing Korean IME handling expects, and keep plain left-arrow suppressed because AppKit already leaves the caret in place. Before: <img width="375" height="375" alt="before" src="https://github.com/user-attachments/assets/1073b93f-625a-4881-8f95-67adefe9d3da" /> After: <img width="375" height="375" alt="after" src="https://github.com/user-attachments/assets/3e4be2a5-4df9-4cdd-bc95-e178ca44c7e7" /> AI usage: OpenAI Codex helped investigate, implement, test, and refine this change. I reviewed and tested the resulting code.
This commit is contained in:
@@ -1138,22 +1138,25 @@ extension Ghostty {
|
||||
// actually delete the prior input characters (prior to the composing).
|
||||
let composing = markedText.length > 0 || markedTextBefore
|
||||
|
||||
// Korean IMEs on macOS may commit preedit text via insertText
|
||||
// while handling an arrow key. Send that committed text separately
|
||||
// before replaying arrow movement, except for plain left-arrow
|
||||
// where AppKit already leaves the caret in place.
|
||||
// The input method may commit all or part of the preedit text via
|
||||
// insertText while handling a key that should not itself be
|
||||
// encoded. Send that committed text separately, then only replay
|
||||
// keys that should still affect the terminal after committing.
|
||||
if markedTextBefore,
|
||||
markedText.length == 0,
|
||||
let list = keyTextAccumulator,
|
||||
list.count > 0,
|
||||
let preeditCommitArrow = preeditCommitArrowKey(translationEvent) {
|
||||
list.count > 0 {
|
||||
for text in list {
|
||||
if Ghostty.SurfaceView.shouldSuppressComposingControlInput(
|
||||
text,
|
||||
composing: composing
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
_ = committedPreeditTextAction(action, text: text)
|
||||
}
|
||||
|
||||
let isPlainLeftArrow = preeditCommitArrow == .arrowLeft &&
|
||||
event.modifierFlags.isDisjoint(with: [.shift, .control, .option, .command])
|
||||
if !isPlainLeftArrow {
|
||||
if shouldReplayCommittedPreeditKey(translationEvent) {
|
||||
_ = keyAction(
|
||||
action,
|
||||
event: event,
|
||||
@@ -1436,13 +1439,17 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
private func preeditCommitArrowKey(_ event: NSEvent) -> Ghostty.Input.Key? {
|
||||
guard let key = Ghostty.Input.Key(keyCode: event.keyCode) else { return nil }
|
||||
private func shouldReplayCommittedPreeditKey(_ event: NSEvent) -> Bool {
|
||||
guard let key = Ghostty.Input.Key(keyCode: event.keyCode) else { return false }
|
||||
switch key {
|
||||
case .arrowDown, .arrowLeft, .arrowRight, .arrowUp:
|
||||
return key
|
||||
case .arrowDown, .arrowRight, .arrowUp:
|
||||
return true
|
||||
case .arrowLeft:
|
||||
// Don't replay plain left-arrow because AppKit already leaves
|
||||
// the caret in place after Korean IMEs commit preedit text.
|
||||
return !event.modifierFlags.isDisjoint(with: [.shift, .control, .option, .command])
|
||||
default:
|
||||
return nil
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user