From 96deabfb7816094e2591ab39de2806b92bf6e453 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 13 Nov 2024 10:42:08 -0700 Subject: [PATCH] Deadkeys 2 (#20612) Re-land of #20515 with less brokenness In particular it turns out that for control, the .characters() method returns the control code. This mostly didn't make a difference, except when the control code matched tab/enter/escape (for ctrl-y,ctrl-[/ctrl-c) as we interpreted the key incorrectly. Secondly, we were setting IME key too aggressively. This led to (in vim mode) cmd-shift-{ being interpreted as [, so vim would wait for a second [ before letting you change tab. Release Notes: - N/A --- Cargo.lock | 2 +- crates/gpui/examples/input.rs | 4 +- crates/gpui/src/platform.rs | 14 ++ crates/gpui/src/platform/keystroke.rs | 4 + crates/gpui/src/platform/mac/events.rs | 182 ++++++++------ crates/gpui/src/platform/mac/platform.rs | 20 +- crates/gpui/src/platform/mac/window.rs | 246 ++++++++----------- crates/terminal_view/src/terminal_element.rs | 4 + 8 files changed, 252 insertions(+), 224 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a40fc96aa..ae36a56208 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5661,7 +5661,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.7", "tokio", "tower-service", "tracing", diff --git a/crates/gpui/examples/input.rs b/crates/gpui/examples/input.rs index 1c7e285e7f..d52697c43f 100644 --- a/crates/gpui/examples/input.rs +++ b/crates/gpui/examples/input.rs @@ -580,9 +580,9 @@ impl Render for InputExample { .children(self.recent_keystrokes.iter().rev().map(|ks| { format!( "{:} {}", - ks, + ks.unparse(), if let Some(ime_key) = ks.ime_key.as_ref() { - format!("-> {}", ime_key) + format!("-> {:?}", ime_key) } else { "".to_owned() } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 12b294e680..a8424d197a 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -688,6 +688,11 @@ 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); } @@ -785,6 +790,15 @@ pub trait InputHandler: 'static { range_utf16: Range, cx: &mut WindowContext, ) -> Option>; + + /// 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 diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index 73cd83d123..f61beab9e9 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -34,6 +34,7 @@ impl Keystroke { { let ime_modifiers = Modifiers { control: self.modifiers.control, + platform: self.modifiers.platform, ..Default::default() }; @@ -124,6 +125,9 @@ 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-"); } diff --git a/crates/gpui/src/platform/mac/events.rs b/crates/gpui/src/platform/mac/events.rs index 3203709487..aeff08ada8 100644 --- a/crates/gpui/src/platform/mac/events.rs +++ b/crates/gpui/src/platform/mac/events.rs @@ -1,20 +1,20 @@ use crate::{ - platform::mac::NSStringExt, point, px, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, - ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, - MouseUpEvent, NavigationDirection, Pixels, PlatformInput, ScrollDelta, ScrollWheelEvent, - TouchPhase, + 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, }; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType}, base::{id, YES}, }; -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}; +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}; const BACKSPACE_KEY: u16 = 0x7f; const SPACE_KEY: u16 = b' ' as u16; @@ -24,24 +24,6 @@ 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 { use cocoa::appkit::*; let code = match key { @@ -259,8 +241,12 @@ impl PlatformInput { unsafe fn parse_keystroke(native_event: id) -> Keystroke { use cocoa::appkit::*; - 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 mut characters = native_event + .charactersIgnoringModifiers() + .to_str() + .to_string(); + let mut ime_key = None; + let first_char = characters.chars().next().map(|ch| ch as u16); let modifiers = native_event.modifierFlags(); let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask); @@ -313,7 +299,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { _ => { // Cases to test when modifying this: // - // qwerty key | none | cmd | cmd-shift + // qwerty key | none | cmd | cmd-shift // * Armenian s | ս | cmd-s | cmd-shift-s (layout is non-ASCII, so we use cmd layout) // * Dvorak+QWERTY s | o | cmd-s | cmd-shift-s (layout switches on cmd) // * Ukrainian+QWERTY s | с | cmd-s | cmd-shift-s (macOS reports cmd-s instead of cmd-S) @@ -321,12 +307,17 @@ 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_with_shift = chars_for_modified_key(native_event.keyCode(), false, true); + // + let mut chars_ignoring_modifiers = + chars_for_modified_key(native_event.keyCode(), NO_MOD); + let mut chars_with_shift = chars_for_modified_key(native_event.keyCode(), SHIFT_MOD); + let always_use_cmd_layout = always_use_command_layout(); // Handle Dvorak+QWERTY / Russian / Armeniam - if command || always_use_command_layout() { - let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), true, false); - let chars_with_both = chars_for_modified_key(native_event.keyCode(), true, true); + if command || always_use_cmd_layout { + let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), CMD_MOD); + let chars_with_both = + chars_for_modified_key(native_event.keyCode(), CMD_MOD | SHIFT_MOD); // We don't do this in the case that the shifted command key generates // the same character as the unshifted command key (Norwegian, e.g.) @@ -341,14 +332,34 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { chars_ignoring_modifiers = chars_with_cmd; } - if shift && chars_ignoring_modifiers == chars_with_shift.to_ascii_lowercase() { + let mut key = if shift + && chars_ignoring_modifiers + .chars() + .all(|c| c.is_ascii_lowercase()) + { chars_ignoring_modifiers } else if shift { shift = false; chars_with_shift } else { chars_ignoring_modifiers - } + }; + + if always_use_cmd_layout || alt { + let mut mods = NO_MOD; + if shift { + mods |= SHIFT_MOD; + } + if alt { + mods |= OPTION_MOD; + } + let alt_key = chars_for_modified_key(native_event.keyCode(), mods); + if alt_key != key { + ime_key = Some(alt_key); + } + }; + + key } }; @@ -361,50 +372,83 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { function, }, key, - ime_key: None, + ime_key, } } fn always_use_command_layout() -> bool { - // 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() { + if chars_for_modified_key(0, NO_MOD).is_ascii() { return false; } - 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() + chars_for_modified_key(0, CMD_MOD).is_ascii() } -fn chars_for_modified_key(code: CGKeyCode, cmd: bool, shift: bool) -> String { - // 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); +const NO_MOD: u32 = 0; +const CMD_MOD: u32 = 1; +const SHIFT_MOD: u32 = 2; +const OPTION_MOD: u32 = 8; - let mut flags = CGEventFlags::empty(); - if cmd { - flags |= CGEventFlags::CGEventFlagCommand; +fn chars_for_modified_key(code: CGKeyCode, modifiers: u32) -> 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 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; + + 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(); } - if shift { - flags |= CGEventFlags::CGEventFlagShift; + 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(); } - event.set_flags(flags); + let keyboard_layout = unsafe { CFDataGetBytePtr(layout_data) }; unsafe { - let event: id = msg_send![class!(NSEvent), eventWithCGEvent: &*event]; - event.characters().to_str().to_string() + 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]; } + String::from_utf16(&buffer[..buffer_size]).unwrap_or_default() } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 332c958410..0e0b9e0468 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -1448,13 +1448,27 @@ unsafe fn ns_url_to_path(url: id) -> Result { #[link(name = "Carbon", kind = "framework")] extern "C" { - fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut Object; - fn TISGetInputSourceProperty( + pub(super) fn TISCopyCurrentKeyboardLayoutInputSource() -> *mut Object; + pub(super) fn TISGetInputSourceProperty( inputSource: *mut Object, propertyKey: *const c_void, ) -> *mut Object; - pub static kTISPropertyInputSourceID: CFStringRef; + 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; } mod security { diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 0efc51290b..e9c3f0a6bd 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -38,7 +38,6 @@ use std::{ cell::Cell, ffi::{c_void, CStr}, mem, - ops::Range, path::PathBuf, ptr::{self, NonNull}, rc::Rc, @@ -310,14 +309,6 @@ 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>), - SetMarkedText(String, Option>, Option>), - UnmarkText, -} - struct MacWindowState { handle: AnyWindowHandle, executor: ForegroundExecutor, @@ -338,14 +329,11 @@ struct MacWindowState { synthetic_drag_counter: usize, traffic_light_position: Option>, previous_modifiers_changed_event: Option, - // State tracking what the IME did after the last request - last_ime_inputs: Option>); 1]>>, - previous_keydown_inserted_text: Option, + keystroke_for_do_command: Option, external_files_dragged: bool, // Whether the next left-mouse click is also the focusing click. first_mouse: bool, fullscreen_restore_bounds: Bounds, - ime_composing: bool, } impl MacWindowState { @@ -619,12 +607,10 @@ impl MacWindow { .as_ref() .and_then(|titlebar| titlebar.traffic_light_position), previous_modifiers_changed_event: None, - last_ime_inputs: None, - previous_keydown_inserted_text: None, + keystroke_for_do_command: None, external_files_dragged: false, first_mouse: false, fullscreen_restore_bounds: Bounds::default(), - ime_composing: false, }))); (*native_window).set_ivar( @@ -1226,9 +1212,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 $) @@ -1241,95 +1227,80 @@ 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)) }; - 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; + 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; + } + + 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; } - let keydown = event.keystroke.clone(); - let fn_modifier = keydown.modifiers.function; - lock.last_ime_inputs = Some(Default::default()); - drop(lock); + 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; + } - // 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 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 = lock.event_callback.take(); - drop(lock); - - 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); - } - } - } - - window_state.lock().event_callback = callback; - - handled as BOOL + 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; + } + + 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; + } + NO + }); + if handled == Some(YES) { + return YES; + } + } + + unsafe { + let input_context: id = msg_send![this, inputContext]; + msg_send![input_context, handleEvent: native_event] } } @@ -1741,10 +1712,9 @@ 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(); - send_to_input_handler( - this, - ImeInput::InsertText(text.to_string(), replacement_range), - ); + with_input_handler(this, |input_handler| { + input_handler.replace_text_in_range(replacement_range, &text) + }); } } @@ -1766,15 +1736,13 @@ 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(); - - send_to_input_handler( - this, - ImeInput::SetMarkedText(text.to_string(), replacement_range, selected_range), - ); + with_input_handler(this, |input_handler| { + input_handler.replace_and_mark_text_in_range(replacement_range, &text, selected_range) + }); } } extern "C" fn unmark_text(this: &Object, _: Sel) { - send_to_input_handler(this, ImeInput::UnmarkText); + with_input_handler(this, |input_handler| input_handler.unmark_text()); } extern "C" fn attributed_substring_for_proposed_range( @@ -1800,7 +1768,24 @@ extern "C" fn attributed_substring_for_proposed_range( .unwrap_or(nil) } -extern "C" fn do_command_by_selector(_: &Object, _: Sel, _: Sel) {} +// 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 view_did_change_effective_appearance(this: &Object, _: Sel) { unsafe { @@ -1950,43 +1935,6 @@ 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"); diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 5998caa0da..bc4f58a5ef 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -1044,6 +1044,10 @@ impl InputHandler for TerminalInputHandler { ) -> Option> { self.cursor_bounds } + + fn apple_press_and_hold_enabled(&mut self) -> bool { + false + } } pub fn is_blank(cell: &IndexedCell) -> bool {