diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 758af61cd1..6f9330607f 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -311,6 +311,7 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C } #[allow(clippy::enum_variant_names)] +#[derive(Clone)] enum ImeInput { InsertText(String, Option>), SetMarkedText(String, Option>, Option>), @@ -339,7 +340,7 @@ struct MacWindowState { traffic_light_position: Option>, previous_modifiers_changed_event: Option, // State tracking what the IME did after the last request - input_during_keydown: Option>, + last_ime_inputs: Option>); 1]>>, previous_keydown_inserted_text: Option, external_files_dragged: bool, // Whether the next left-mouse click is also the focusing click. @@ -635,7 +636,7 @@ impl MacWindow { .as_ref() .and_then(|titlebar| titlebar.traffic_light_position), previous_modifiers_changed_event: None, - input_during_keydown: None, + last_ime_inputs: None, previous_keydown_inserted_text: None, external_files_dragged: false, first_mouse: false, @@ -1190,14 +1191,30 @@ extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) { } // Things to test if you're modifying this method: +// U.S. layout: +// - The IME consumes characters like 'j' and 'k', which makes paging through `less` in +// the terminal behave incorrectly by default. This behavior should be patched by our +// IME integration +// - `alt-t` should open the tasks menu +// - In vim mode, this keybinding should work: +// ``` +// { +// "context": "Editor && vim_mode == insert", +// "bindings": {"j j": "vim::NormalBefore"} +// } +// ``` +// and typing 'j k' in insert mode with this keybinding should insert the two characters // Brazilian layout: -// - `" space` should type a quote +// - `" space` should create an unmarked quote // - `" backspace` should delete the marked quote -// - `" up` should type the quote, unmark it, and move up one line -// - `" cmd-down` should not leave a marked quote behind (it maybe should dispatch the key though?) +// - `" 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 $) +// Japanese (Romaji) layout: +// - type `a i left down up enter enter` should create an unmarked text "愛" extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL { let window_state = unsafe { get_window_state(this) }; let mut lock = window_state.as_ref().lock(); @@ -1227,7 +1244,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: } else { lock.last_fresh_keydown = Some(keydown.clone()); } - lock.input_during_keydown = Some(SmallVec::new()); + lock.last_ime_inputs = Some(Default::default()); drop(lock); // Send the event to the input context for IME handling, unless the `fn` modifier is @@ -1243,15 +1260,16 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: let mut handled = false; let mut lock = window_state.lock(); let previous_keydown_inserted_text = lock.previous_keydown_inserted_text.take(); - let mut input_during_keydown = lock.input_during_keydown.take().unwrap(); + let mut last_inserts = lock.last_ime_inputs.take().unwrap(); + let mut callback = lock.event_callback.take(); drop(lock); - let last_ime = input_during_keydown.pop(); + 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 ime in input_during_keydown { - send_to_input_handler(this, ime); + for (text, range) in last_inserts { + send_to_input_handler(this, ImeInput::InsertText(text, range)); } let is_composing = @@ -1259,20 +1277,18 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: .flatten() .is_some(); - if let Some(ime) = last_ime { - if let ImeInput::InsertText(text, _) = &ime { - 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 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, ime); + send_to_input_handler(this, ImeInput::InsertText(text, range)); } } else if !is_composing { let is_held = event.is_held; @@ -1654,21 +1670,24 @@ extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id { } extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL { - with_input_handler(this, |input_handler| input_handler.marked_text_range()) - .flatten() - .is_some() as BOOL + let has_marked_text_result = + with_input_handler(this, |input_handler| input_handler.marked_text_range()).flatten(); + + has_marked_text_result.is_some() as BOOL } extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange { - with_input_handler(this, |input_handler| input_handler.marked_text_range()) - .flatten() - .map_or(NSRange::invalid(), |range| range.into()) + let marked_range_result = + with_input_handler(this, |input_handler| input_handler.marked_text_range()).flatten(); + + marked_range_result.map_or(NSRange::invalid(), |range| range.into()) } extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange { - with_input_handler(this, |input_handler| input_handler.selected_text_range()) - .flatten() - .map_or(NSRange::invalid(), |range| range.into()) + let selected_range_result = + with_input_handler(this, |input_handler| input_handler.selected_text_range()).flatten(); + + selected_range_result.map_or(NSRange::invalid(), |range| range.into()) } extern "C" fn first_rect_for_character_range( @@ -1761,7 +1780,7 @@ extern "C" fn attributed_substring_for_proposed_range( return None; } - let selected_text = input_handler.text_for_range(range)?; + let selected_text = input_handler.text_for_range(range.clone())?; unsafe { let string: id = msg_send![class!(NSAttributedString), alloc]; let string: id = msg_send![string, initWithString: ns_string(&selected_text)]; @@ -1926,20 +1945,26 @@ 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(ime_input) = lock.input_during_keydown.as_mut() { - ime_input.push(ime); - return; - } + if let Some(mut input_handler) = lock.input_handler.take() { - drop(lock); - match ime { + match ime.clone() { 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) => { + drop(lock); input_handler.replace_and_mark_text_in_range(range, &text, marked_range) } - ImeInput::UnmarkText => input_handler.unmark_text(), + ImeInput::UnmarkText => { + drop(lock); + input_handler.unmark_text() + } } window_state.lock().input_handler = Some(input_handler); }