diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index dfb4824cd96c3db259e6fbe4a69c4f975897990a..8973677668a3f483da39f1dceef0a08b1f27db43 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -108,6 +108,10 @@ unsafe fn build_classes() { sel!(keyDown:), handle_key_down as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(keyUp:), + handle_key_up as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(mouseDown:), handle_view_event as extern "C" fn(&Object, Sel, id), @@ -1219,6 +1223,10 @@ extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) { handle_key_event(this, native_event, false); } +extern "C" fn handle_key_up(this: &Object, _: Sel, native_event: id) { + handle_key_event(this, native_event, false); +} + // 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 @@ -1251,101 +1259,113 @@ 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 { + let Some(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. - // We also do this for non-printing keys (like arrow keys and escape) as the IME menu - // may need them even if there is no marked text; - // however we skip keys with control or the input handler adds control-characters to the buffer. - // and keys with function, as the input handler swallows them. - if is_composing - || (event.keystroke.key_char.is_none() - && !event.keystroke.modifiers.control - && !event.keystroke.modifiers.function) - { - { - let mut lock = window_state.as_ref().lock(); - lock.keystroke_for_do_command = Some(event.keystroke.clone()); - lock.do_command_handled.take(); - drop(lock); - } - - 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 let Some(handled) = window_state.as_ref().lock().do_command_handled.take() { - return handled as BOOL; - } else if handled == YES { - return YES; - } + let run_callback = |event: PlatformInput| -> BOOL { 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 + !callback(event).propagate as BOOL } else { NO }; window_state.as_ref().lock().event_callback = callback; - return 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 + handled }; - window_state.as_ref().lock().event_callback = callback; - if handled == YES { - return YES; - } - if event.is_held { - if let Some(key_char) = event.keystroke.key_char.as_ref() { - let handled = with_input_handler(&this, |input_handler| { - if !input_handler.apple_press_and_hold_enabled() { - input_handler.replace_text_in_range(None, &key_char); + match event { + PlatformInput::KeyDown(mut key_down_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(key_down_event.clone()); + } else if lock.last_key_equivalent.take().as_ref() == Some(&key_down_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. + // We also do this for non-printing keys (like arrow keys and escape) as the IME menu + // may need them even if there is no marked text; + // however we skip keys with control or the input handler adds control-characters to the buffer. + // and keys with function, as the input handler swallows them. + if is_composing + || (key_down_event.keystroke.key_char.is_none() + && !key_down_event.keystroke.modifiers.control + && !key_down_event.keystroke.modifiers.function) + { + { + let mut lock = window_state.as_ref().lock(); + lock.keystroke_for_do_command = Some(key_down_event.keystroke.clone()); + lock.do_command_handled.take(); + drop(lock); + } + + 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 let Some(handled) = window_state.as_ref().lock().do_command_handled.take() { + return handled as BOOL; + } else if handled == YES { return YES; } - NO - }); - if handled == Some(YES) { + + let handled = run_callback(PlatformInput::KeyDown(key_down_event)); + return handled; + } + + let handled = run_callback(PlatformInput::KeyDown(key_down_event.clone())); + if handled == YES { return YES; } + + if key_down_event.is_held { + if let Some(key_char) = key_down_event.keystroke.key_char.as_ref() { + let handled = with_input_handler(&this, |input_handler| { + if !input_handler.apple_press_and_hold_enabled() { + input_handler.replace_text_in_range(None, &key_char); + return YES; + } + NO + }); + if handled == Some(YES) { + return YES; + } + } + } + + // Don't send key equivalents to the input handler, + // or macOS shortcuts like cmd-` will stop working. + if key_equivalent { + return NO; + } + + unsafe { + let input_context: id = msg_send![this, inputContext]; + msg_send![input_context, handleEvent: native_event] + } } - } - // Don't send key equivalents to the input handler, - // or macOS shortcuts like cmd-` will stop working. - if key_equivalent { - return NO; - } + PlatformInput::KeyUp(_) => { + drop(lock); + run_callback(event) + } - unsafe { - let input_context: id = msg_send![this, inputContext]; - msg_send![input_context, handleEvent: native_event] + _ => NO, } } diff --git a/crates/gpui/src/platform/windows/events.rs b/crates/gpui/src/platform/windows/events.rs index ae88d224a599036baa840741de8385b225efadea..24c766c891c86edcaf0a72da4305e44bdbd263f6 100644 --- a/crates/gpui/src/platform/windows/events.rs +++ b/crates/gpui/src/platform/windows/events.rs @@ -361,7 +361,7 @@ fn handle_keydown_msg( lparam: LPARAM, state_ptr: Rc, ) -> Option { - let Some(keystroke_or_modifier) = parse_keydown_msg_keystroke(wparam) else { + let Some(keystroke_or_modifier) = parse_keystroke_from_vkey(wparam, false) else { return Some(1); }; let mut lock = state_ptr.state.borrow_mut(); @@ -391,7 +391,7 @@ fn handle_keydown_msg( } fn handle_keyup_msg(wparam: WPARAM, state_ptr: Rc) -> Option { - let Some(keystroke_or_modifier) = parse_keydown_msg_keystroke(wparam) else { + let Some(keystroke_or_modifier) = parse_keystroke_from_vkey(wparam, true) else { return Some(1); }; let mut lock = state_ptr.state.borrow_mut(); @@ -1288,7 +1288,7 @@ enum KeystrokeOrModifier { Modifier(Modifiers), } -fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option { +fn parse_keystroke_from_vkey(wparam: WPARAM, is_keyup: bool) -> Option { let vk_code = wparam.loword(); let modifiers = current_modifiers(); @@ -1316,7 +1316,7 @@ fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option { return Some(KeystrokeOrModifier::Modifier(modifiers)); } - if modifiers.control || modifiers.alt { + if modifiers.control || modifiers.alt || is_keyup { let basic_key = basic_vkcode_to_string(vk_code, modifiers); if let Some(basic_key) = basic_key { return Some(KeystrokeOrModifier::Keystroke(basic_key));