Revert "macOS: Improve deadkeys (#20515)" (#20570)

Max Brunsfeld created

This reverts commit https://github.com/zed-industries/zed/pull/20515

I'm reverting for now to fix issues with key bindings on Nightly:
* `ctrl-c` and `ctrl-m` are being treated as `ctrl-enter`
* `ctrl-[` isn't working in vim mode
* there's a delay before `cmd-shift-[` switches tabs w/ vim mode enabled

Release Notes:

- N/A

Change summary

Cargo.lock                                   |   2 
crates/gpui/examples/input.rs                |   2 
crates/gpui/src/platform.rs                  |  14 -
crates/gpui/src/platform/keystroke.rs        |   3 
crates/gpui/src/platform/mac/events.rs       | 153 +++++--------
crates/gpui/src/platform/mac/platform.rs     |  20 -
crates/gpui/src/platform/mac/window.rs       | 238 +++++++++++++--------
crates/terminal_view/src/terminal_element.rs |   4 
8 files changed, 213 insertions(+), 223 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -5658,7 +5658,7 @@ dependencies = [
  "httpdate",
  "itoa",
  "pin-project-lite",
- "socket2 0.5.7",
+ "socket2 0.4.10",
  "tokio",
  "tower-service",
  "tracing",

crates/gpui/examples/input.rs πŸ”—

@@ -580,7 +580,7 @@ impl Render for InputExample {
             .children(self.recent_keystrokes.iter().rev().map(|ks| {
                 format!(
                     "{:} {}",
-                    ks.unparse(),
+                    ks,
                     if let Some(ime_key) = ks.ime_key.as_ref() {
                         format!("-> {}", ime_key)
                     } else {

crates/gpui/src/platform.rs πŸ”—

@@ -688,11 +688,6 @@ impl PlatformInputHandler {
             .flatten()
     }
 
-    #[allow(dead_code)]
-    fn apple_press_and_hold_enabled(&mut self) -> bool {
-        self.handler.apple_press_and_hold_enabled()
-    }
-
     pub(crate) fn dispatch_input(&mut self, input: &str, cx: &mut WindowContext) {
         self.handler.replace_text_in_range(None, input, cx);
     }
@@ -790,15 +785,6 @@ pub trait InputHandler: 'static {
         range_utf16: Range<usize>,
         cx: &mut WindowContext,
     ) -> Option<Bounds<Pixels>>;
-
-    /// Allows a given input context to opt into getting raw key repeats instead of
-    /// sending these to the platform.
-    /// TODO: Ideally we should be able to set ApplePressAndHoldEnabled in NSUserDefaults
-    /// (which is how iTerm does it) but it doesn't seem to work for me.
-    #[allow(dead_code)]
-    fn apple_press_and_hold_enabled(&mut self) -> bool {
-        true
-    }
 }
 
 /// The variables that can be configured when creating a new window

crates/gpui/src/platform/keystroke.rs πŸ”—

@@ -124,9 +124,6 @@ impl Keystroke {
     /// Produces a representation of this key that Parse can understand.
     pub fn unparse(&self) -> String {
         let mut str = String::new();
-        if self.modifiers.function {
-            str.push_str("fn-");
-        }
         if self.modifiers.control {
             str.push_str("ctrl-");
         }

crates/gpui/src/platform/mac/events.rs πŸ”—

@@ -1,20 +1,20 @@
 use crate::{
-    platform::mac::{
-        kTISPropertyUnicodeKeyLayoutData, LMGetKbdType, NSStringExt,
-        TISCopyCurrentKeyboardLayoutInputSource, TISGetInputSourceProperty, UCKeyTranslate,
-    },
-    point, px, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
-    MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels,
-    PlatformInput, ScrollDelta, ScrollWheelEvent, TouchPhase,
+    platform::mac::NSStringExt, point, px, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
+    ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent,
+    MouseUpEvent, NavigationDirection, Pixels, PlatformInput, ScrollDelta, ScrollWheelEvent,
+    TouchPhase,
 };
 use cocoa::{
     appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
     base::{id, YES},
 };
-use core_foundation::data::{CFDataGetBytePtr, CFDataRef};
-use core_graphics::event::CGKeyCode;
-use objc::{msg_send, sel, sel_impl};
-use std::{borrow::Cow, ffi::c_void};
+use core_graphics::{
+    event::{CGEvent, CGEventFlags, CGKeyCode},
+    event_source::{CGEventSource, CGEventSourceStateID},
+};
+use metal::foreign_types::ForeignType as _;
+use objc::{class, msg_send, sel, sel_impl};
+use std::{borrow::Cow, mem, ptr, sync::Once};
 
 const BACKSPACE_KEY: u16 = 0x7f;
 const SPACE_KEY: u16 = b' ' as u16;
@@ -24,6 +24,24 @@ const ESCAPE_KEY: u16 = 0x1b;
 const TAB_KEY: u16 = 0x09;
 const SHIFT_TAB_KEY: u16 = 0x19;
 
+fn synthesize_keyboard_event(code: CGKeyCode) -> CGEvent {
+    static mut EVENT_SOURCE: core_graphics::sys::CGEventSourceRef = ptr::null_mut();
+    static INIT_EVENT_SOURCE: Once = Once::new();
+
+    INIT_EVENT_SOURCE.call_once(|| {
+        let source = CGEventSource::new(CGEventSourceStateID::Private).unwrap();
+        unsafe {
+            EVENT_SOURCE = source.as_ptr();
+        };
+        mem::forget(source);
+    });
+
+    let source = unsafe { core_graphics::event_source::CGEventSource::from_ptr(EVENT_SOURCE) };
+    let event = CGEvent::new_keyboard_event(source.clone(), code, true).unwrap();
+    mem::forget(source);
+    event
+}
+
 pub fn key_to_native(key: &str) -> Cow<str> {
     use cocoa::appkit::*;
     let code = match key {
@@ -241,9 +259,8 @@ impl PlatformInput {
 unsafe fn parse_keystroke(native_event: id) -> Keystroke {
     use cocoa::appkit::*;
 
-    let mut characters = native_event.characters().to_str().to_string();
-    let mut ime_key = None;
-    let first_char = characters.chars().next().map(|ch| ch as u16);
+    let mut chars_ignoring_modifiers = chars_for_modified_key(native_event.keyCode(), false, false);
+    let first_char = chars_ignoring_modifiers.chars().next().map(|ch| ch as u16);
     let modifiers = native_event.modifierFlags();
 
     let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
@@ -304,9 +321,6 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
             // * Norwegian        7 | 7    | cmd-7 | cmd-/        (macOS reports cmd-shift-7 instead of cmd-/)
             // * Russian          7 | 7    | cmd-7 | cmd-&        (shift-7 is . but when cmd is down, should use cmd layout)
             // * German QWERTZ    ; | ΓΆ    | cmd-ΓΆ | cmd-Γ–        (Zed's shift special case only applies to a-z)
-            //
-            let mut chars_ignoring_modifiers =
-                chars_for_modified_key(native_event.keyCode(), false, false);
             let mut chars_with_shift = chars_for_modified_key(native_event.keyCode(), false, true);
 
             // Handle Dvorak+QWERTY / Russian / Armeniam
@@ -327,24 +341,14 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
                 chars_ignoring_modifiers = chars_with_cmd;
             }
 
-            let mut key = if shift
-                && chars_ignoring_modifiers
-                    .chars()
-                    .all(|c| c.is_ascii_lowercase())
-            {
+            if shift && chars_ignoring_modifiers == chars_with_shift.to_ascii_lowercase() {
                 chars_ignoring_modifiers
             } else if shift {
                 shift = false;
                 chars_with_shift
             } else {
                 chars_ignoring_modifiers
-            };
-
-            if characters.len() > 0 && characters != key {
-                ime_key = Some(characters.clone());
-            };
-
-            key
+            }
         }
     };
 
@@ -357,81 +361,50 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
             function,
         },
         key,
-        ime_key,
+        ime_key: None,
     }
 }
 
 fn always_use_command_layout() -> bool {
-    if chars_for_modified_key(0, false, false).is_ascii() {
+    // look at the key to the right of "tab" ('a' in QWERTY)
+    // if it produces a non-ASCII character, but with command held produces ASCII,
+    // we default to the command layout for our keyboard system.
+    let event = synthesize_keyboard_event(0);
+    let without_cmd = unsafe {
+        let event: id = msg_send![class!(NSEvent), eventWithCGEvent: &*event];
+        event.characters().to_str().to_string()
+    };
+    if without_cmd.is_ascii() {
         return false;
     }
 
-    chars_for_modified_key(0, true, false).is_ascii()
+    event.set_flags(CGEventFlags::CGEventFlagCommand);
+    let with_cmd = unsafe {
+        let event: id = msg_send![class!(NSEvent), eventWithCGEvent: &*event];
+        event.characters().to_str().to_string()
+    };
+
+    with_cmd.is_ascii()
 }
 
 fn chars_for_modified_key(code: CGKeyCode, cmd: bool, shift: bool) -> String {
-    // Values from: https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.6.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h#L126
-    // shifted >> 8 for UCKeyTranslate
-    const CMD_MOD: u32 = 1;
-    const SHIFT_MOD: u32 = 2;
-    const CG_SPACE_KEY: u16 = 49;
-    // https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.6.sdk/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/Headers/UnicodeUtilities.h#L278
-    #[allow(non_upper_case_globals)]
-    const kUCKeyActionDown: u16 = 0;
-    #[allow(non_upper_case_globals)]
-    const kUCKeyTranslateNoDeadKeysMask: u32 = 0;
+    // Ideally, we would use `[NSEvent charactersByApplyingModifiers]` but that
+    // always returns an empty string with certain keyboards, e.g. Japanese. Synthesizing
+    // an event with the given flags instead lets us access `characters`, which always
+    // returns a valid string.
+    let event = synthesize_keyboard_event(code);
 
-    let keyboard_type = unsafe { LMGetKbdType() as u32 };
-    const BUFFER_SIZE: usize = 4;
-    let mut dead_key_state = 0;
-    let mut buffer: [u16; BUFFER_SIZE] = [0; BUFFER_SIZE];
-    let mut buffer_size: usize = 0;
-
-    let keyboard = unsafe { TISCopyCurrentKeyboardLayoutInputSource() };
-    if keyboard.is_null() {
-        return "".to_string();
+    let mut flags = CGEventFlags::empty();
+    if cmd {
+        flags |= CGEventFlags::CGEventFlagCommand;
     }
-    let layout_data = unsafe {
-        TISGetInputSourceProperty(keyboard, kTISPropertyUnicodeKeyLayoutData as *const c_void)
-            as CFDataRef
-    };
-    if layout_data.is_null() {
-        unsafe {
-            let _: () = msg_send![keyboard, release];
-        }
-        return "".to_string();
+    if shift {
+        flags |= CGEventFlags::CGEventFlagShift;
     }
-    let keyboard_layout = unsafe { CFDataGetBytePtr(layout_data) };
-    let modifiers = if cmd { CMD_MOD } else { 0 } | if shift { SHIFT_MOD } else { 0 };
+    event.set_flags(flags);
 
     unsafe {
-        UCKeyTranslate(
-            keyboard_layout as *const c_void,
-            code,
-            kUCKeyActionDown,
-            modifiers,
-            keyboard_type,
-            kUCKeyTranslateNoDeadKeysMask,
-            &mut dead_key_state,
-            BUFFER_SIZE,
-            &mut buffer_size as *mut usize,
-            &mut buffer as *mut u16,
-        );
-        if dead_key_state != 0 {
-            UCKeyTranslate(
-                keyboard_layout as *const c_void,
-                CG_SPACE_KEY,
-                kUCKeyActionDown,
-                modifiers,
-                keyboard_type,
-                kUCKeyTranslateNoDeadKeysMask,
-                &mut dead_key_state,
-                BUFFER_SIZE,
-                &mut buffer_size as *mut usize,
-                &mut buffer as *mut u16,
-            );
-        }
-        let _: () = msg_send![keyboard, release];
+        let event: id = msg_send![class!(NSEvent), eventWithCGEvent: &*event];
+        event.characters().to_str().to_string()
     }
-    String::from_utf16(&buffer[..buffer_size]).unwrap_or_default()
 }

crates/gpui/src/platform/mac/platform.rs πŸ”—

@@ -1448,27 +1448,13 @@ unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> {
 
 #[link(name = "Carbon", kind = "framework")]
 extern "C" {
-    pub(super) fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut Object;
-    pub(super) fn TISGetInputSourceProperty(
+    fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut Object;
+    fn TISGetInputSourceProperty(
         inputSource: *mut Object,
         propertyKey: *const c_void,
     ) -> *mut Object;
 
-    pub(super) fn UCKeyTranslate(
-        keyLayoutPtr: *const ::std::os::raw::c_void,
-        virtualKeyCode: u16,
-        keyAction: u16,
-        modifierKeyState: u32,
-        keyboardType: u32,
-        keyTranslateOptions: u32,
-        deadKeyState: *mut u32,
-        maxStringLength: usize,
-        actualStringLength: *mut usize,
-        unicodeString: *mut u16,
-    ) -> u32;
-    pub(super) fn LMGetKbdType() -> u16;
-    pub(super) static kTISPropertyUnicodeKeyLayoutData: CFStringRef;
-    pub(super) static kTISPropertyInputSourceID: CFStringRef;
+    pub static kTISPropertyInputSourceID: CFStringRef;
 }
 
 mod security {

crates/gpui/src/platform/mac/window.rs πŸ”—

@@ -38,6 +38,7 @@ use std::{
     cell::Cell,
     ffi::{c_void, CStr},
     mem,
+    ops::Range,
     path::PathBuf,
     ptr::{self, NonNull},
     rc::Rc,
@@ -309,6 +310,14 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C
     decl.register()
 }
 
+#[allow(clippy::enum_variant_names)]
+#[derive(Clone, Debug)]
+enum ImeInput {
+    InsertText(String, Option<Range<usize>>),
+    SetMarkedText(String, Option<Range<usize>>, Option<Range<usize>>),
+    UnmarkText,
+}
+
 struct MacWindowState {
     handle: AnyWindowHandle,
     executor: ForegroundExecutor,
@@ -329,11 +338,14 @@ struct MacWindowState {
     synthetic_drag_counter: usize,
     traffic_light_position: Option<Point<Pixels>>,
     previous_modifiers_changed_event: Option<PlatformInput>,
-    keystroke_for_do_command: Option<Keystroke>,
+    // State tracking what the IME did after the last request
+    last_ime_inputs: Option<SmallVec<[(String, Option<Range<usize>>); 1]>>,
+    previous_keydown_inserted_text: Option<String>,
     external_files_dragged: bool,
     // Whether the next left-mouse click is also the focusing click.
     first_mouse: bool,
     fullscreen_restore_bounds: Bounds<Pixels>,
+    ime_composing: bool,
 }
 
 impl MacWindowState {
@@ -607,10 +619,12 @@ impl MacWindow {
                     .as_ref()
                     .and_then(|titlebar| titlebar.traffic_light_position),
                 previous_modifiers_changed_event: None,
-                keystroke_for_do_command: None,
+                last_ime_inputs: None,
+                previous_keydown_inserted_text: None,
                 external_files_dragged: false,
                 first_mouse: false,
                 fullscreen_restore_bounds: Bounds::default(),
+                ime_composing: false,
             })));
 
             (*native_window).set_ivar(
@@ -1212,9 +1226,9 @@ extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
 //  Brazilian layout:
 //   - `" space` should create an unmarked quote
 //   - `" backspace` should delete the marked quote
-//   - `" "`should create an unmarked quote and a second marked quote
 //   - `" up` should insert a quote, unmark it, and move up one line
 //   - `" cmd-down` should insert a quote, unmark it, and move to the end of the file
+//      - NOTE: The current implementation does not move the selection to the end of the file
 //   - `cmd-ctrl-space` and clicking on an emoji should type it
 //  Czech (QWERTY) layout:
 //   - in vim mode `option-4`  should go to end of line (same as $)
@@ -1227,80 +1241,95 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
     let window_height = lock.content_size().height;
     let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
 
-    let Some(PlatformInput::KeyDown(mut event)) = event else {
-        return NO;
-    };
-    // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
-    // If that event isn't handled, it will then dispatch a "key down" event. GPUI
-    // makes no distinction between these two types of events, so we need to ignore
-    // the "key down" event if we've already just processed its "key equivalent" version.
-    if key_equivalent {
-        lock.last_key_equivalent = Some(event.clone());
-    } else if lock.last_key_equivalent.take().as_ref() == Some(&event) {
-        return NO;
-    }
+    if let Some(PlatformInput::KeyDown(mut event)) = event {
+        // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
+        // If that event isn't handled, it will then dispatch a "key down" event. GPUI
+        // makes no distinction between these two types of events, so we need to ignore
+        // the "key down" event if we've already just processed its "key equivalent" version.
+        if key_equivalent {
+            lock.last_key_equivalent = Some(event.clone());
+        } else if lock.last_key_equivalent.take().as_ref() == Some(&event) {
+            return NO;
+        }
 
-    drop(lock);
+        let keydown = event.keystroke.clone();
+        let fn_modifier = keydown.modifiers.function;
+        lock.last_ime_inputs = Some(Default::default());
+        drop(lock);
 
-    let is_composing = with_input_handler(this, |input_handler| input_handler.marked_text_range())
-        .flatten()
-        .is_some();
-
-    // If we're composing, send the key to the input handler first;
-    // otherwise we only send to the input handler if we don't have a matching binding.
-    // The input handler may call `do_command_by_selector` if it doesn't know how to handle
-    // a key. If it does so, it will return YES so we won't send the key twice.
-    if is_composing || event.keystroke.key.is_empty() {
-        window_state.as_ref().lock().keystroke_for_do_command = Some(event.keystroke.clone());
-        let handled: BOOL = unsafe {
-            let input_context: id = msg_send![this, inputContext];
-            msg_send![input_context, handleEvent: native_event]
-        };
-        window_state.as_ref().lock().keystroke_for_do_command.take();
-        if handled == YES {
-            return YES;
+        // Send the event to the input context for IME handling, unless the `fn` modifier is
+        // being pressed.
+        // this will call back into `insert_text`, etc.
+        if !fn_modifier {
+            unsafe {
+                let input_context: id = msg_send![this, inputContext];
+                let _: BOOL = msg_send![input_context, handleEvent: native_event];
+            }
         }
 
-        let mut callback = window_state.as_ref().lock().event_callback.take();
-        let handled: BOOL = if let Some(callback) = callback.as_mut() {
-            !callback(PlatformInput::KeyDown(event)).propagate as BOOL
-        } else {
-            NO
-        };
-        window_state.as_ref().lock().event_callback = callback;
-        return handled as BOOL;
-    }
+        let mut handled = false;
+        let mut lock = window_state.lock();
+        let previous_keydown_inserted_text = lock.previous_keydown_inserted_text.take();
+        let mut last_inserts = lock.last_ime_inputs.take().unwrap();
+        let ime_composing = std::mem::take(&mut lock.ime_composing);
 
-    let mut callback = window_state.as_ref().lock().event_callback.take();
-    let handled = if let Some(callback) = callback.as_mut() {
-        !callback(PlatformInput::KeyDown(event.clone())).propagate as BOOL
-    } else {
-        NO
-    };
-    window_state.as_ref().lock().event_callback = callback;
-    if handled == YES {
-        return YES;
-    }
+        let mut callback = lock.event_callback.take();
+        drop(lock);
 
-    if event.is_held {
-        let handled = with_input_handler(&this, |input_handler| {
-            if !input_handler.apple_press_and_hold_enabled() {
-                input_handler.replace_text_in_range(
-                    None,
-                    &event.keystroke.ime_key.unwrap_or(event.keystroke.key),
-                );
-                return YES;
+        let last_insert = last_inserts.pop();
+        // on a brazilian keyboard typing `"` and then hitting `up` will cause two IME
+        // events, one to unmark the quote, and one to send the up arrow.
+        for (text, range) in last_inserts {
+            send_to_input_handler(this, ImeInput::InsertText(text, range));
+        }
+
+        let is_composing =
+            with_input_handler(this, |input_handler| input_handler.marked_text_range())
+                .flatten()
+                .is_some()
+                || ime_composing;
+
+        if let Some((text, range)) = last_insert {
+            if !is_composing {
+                window_state.lock().previous_keydown_inserted_text = Some(text.clone());
+                if let Some(callback) = callback.as_mut() {
+                    event.keystroke.ime_key = Some(text.clone());
+                    handled = !callback(PlatformInput::KeyDown(event)).propagate;
+                }
+            }
+
+            if !handled {
+                handled = true;
+                send_to_input_handler(this, ImeInput::InsertText(text, range));
+            }
+        } else if !is_composing {
+            let is_held = event.is_held;
+
+            if let Some(callback) = callback.as_mut() {
+                handled = !callback(PlatformInput::KeyDown(event)).propagate;
+            }
+
+            if !handled && is_held {
+                if let Some(text) = previous_keydown_inserted_text {
+                    // macOS IME is a bit funky, and even when you've told it there's nothing to
+                    // enter it will still swallow certain keys (e.g. 'f', 'j') and not others
+                    // (e.g. 'n'). This is a problem for certain kinds of views, like the terminal.
+                    with_input_handler(this, |input_handler| {
+                        if input_handler.selected_text_range(false).is_none() {
+                            handled = true;
+                            input_handler.replace_text_in_range(None, &text)
+                        }
+                    });
+                    window_state.lock().previous_keydown_inserted_text = Some(text);
+                }
             }
-            NO
-        });
-        if handled == Some(YES) {
-            return YES;
         }
-    }
 
-    unsafe {
-        let input_context: id = msg_send![this, inputContext];
-        msg_send![input_context, handleEvent: native_event]
+        window_state.lock().event_callback = callback;
+
+        handled as BOOL
+    } else {
+        NO
     }
 }
 
@@ -1712,9 +1741,10 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS
 
         let text = text.to_str();
         let replacement_range = replacement_range.to_range();
-        with_input_handler(this, |input_handler| {
-            input_handler.replace_text_in_range(replacement_range, &text)
-        });
+        send_to_input_handler(
+            this,
+            ImeInput::InsertText(text.to_string(), replacement_range),
+        );
     }
 }
 
@@ -1736,13 +1766,15 @@ extern "C" fn set_marked_text(
         let selected_range = selected_range.to_range();
         let replacement_range = replacement_range.to_range();
         let text = text.to_str();
-        with_input_handler(this, |input_handler| {
-            input_handler.replace_and_mark_text_in_range(replacement_range, &text, selected_range)
-        });
+
+        send_to_input_handler(
+            this,
+            ImeInput::SetMarkedText(text.to_string(), replacement_range, selected_range),
+        );
     }
 }
 extern "C" fn unmark_text(this: &Object, _: Sel) {
-    with_input_handler(this, |input_handler| input_handler.unmark_text());
+    send_to_input_handler(this, ImeInput::UnmarkText);
 }
 
 extern "C" fn attributed_substring_for_proposed_range(
@@ -1768,24 +1800,7 @@ extern "C" fn attributed_substring_for_proposed_range(
     .unwrap_or(nil)
 }
 
-// We ignore which selector it asks us to do because the user may have
-// bound the shortcut to something else.
-extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
-    let state = unsafe { get_window_state(this) };
-    let mut lock = state.as_ref().lock();
-    let keystroke = lock.keystroke_for_do_command.take();
-    let mut event_callback = lock.event_callback.take();
-    drop(lock);
-
-    if let Some((keystroke, mut callback)) = keystroke.zip(event_callback.as_mut()) {
-        (callback)(PlatformInput::KeyDown(KeyDownEvent {
-            keystroke,
-            is_held: false,
-        }));
-    }
-
-    state.as_ref().lock().event_callback = event_callback;
-}
+extern "C" fn do_command_by_selector(_: &Object, _: Sel, _: Sel) {}
 
 extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
     unsafe {
@@ -1935,6 +1950,43 @@ where
     }
 }
 
+fn send_to_input_handler(window: &Object, ime: ImeInput) {
+    unsafe {
+        let window_state = get_window_state(window);
+        let mut lock = window_state.lock();
+
+        if let Some(mut input_handler) = lock.input_handler.take() {
+            match ime {
+                ImeInput::InsertText(text, range) => {
+                    if let Some(ime_input) = lock.last_ime_inputs.as_mut() {
+                        ime_input.push((text, range));
+                        lock.input_handler = Some(input_handler);
+                        return;
+                    }
+                    drop(lock);
+                    input_handler.replace_text_in_range(range, &text)
+                }
+                ImeInput::SetMarkedText(text, range, marked_range) => {
+                    lock.ime_composing = true;
+                    drop(lock);
+                    input_handler.replace_and_mark_text_in_range(range, &text, marked_range)
+                }
+                ImeInput::UnmarkText => {
+                    drop(lock);
+                    input_handler.unmark_text()
+                }
+            }
+            window_state.lock().input_handler = Some(input_handler);
+        } else {
+            if let ImeInput::InsertText(text, range) = ime {
+                if let Some(ime_input) = lock.last_ime_inputs.as_mut() {
+                    ime_input.push((text, range));
+                }
+            }
+        }
+    }
+}
+
 unsafe fn display_id_for_screen(screen: id) -> CGDirectDisplayID {
     let device_description = NSScreen::deviceDescription(screen);
     let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");

crates/terminal_view/src/terminal_element.rs πŸ”—

@@ -1044,10 +1044,6 @@ impl InputHandler for TerminalInputHandler {
     ) -> Option<Bounds<Pixels>> {
         self.cursor_bounds
     }
-
-    fn apple_press_and_hold_enabled(&mut self) -> bool {
-        false
-    }
 }
 
 pub fn is_blank(cell: &IndexedCell) -> bool {