Disable menu item key equivalents while there are pending keystrokes

Max Brunsfeld created

Change summary

crates/gpui/src/app.rs                   |  9 ++++++
crates/gpui/src/keymap.rs                |  4 +++
crates/gpui/src/platform.rs              |  1 
crates/gpui/src/platform/mac/platform.rs | 31 ++++++++++++++++++++++++-
crates/gpui/src/platform/test.rs         |  3 -
5 files changed, 43 insertions(+), 5 deletions(-)

Detailed changes

crates/gpui/src/app.rs 🔗

@@ -192,11 +192,18 @@ impl App {
                 cx.borrow_mut().quit();
             }
         }));
+        foreground_platform.on_will_open_menu(Box::new({
+            let cx = app.0.clone();
+            move || {
+                let mut cx = cx.borrow_mut();
+                cx.keystroke_matcher.clear_pending();
+            }
+        }));
         foreground_platform.on_validate_menu_command(Box::new({
             let cx = app.0.clone();
             move |action| {
                 let cx = cx.borrow_mut();
-                cx.is_action_available(action)
+                !cx.keystroke_matcher.has_pending_keystrokes() && cx.is_action_available(action)
             }
         }));
         foreground_platform.on_menu_command(Box::new({

crates/gpui/src/keymap.rs 🔗

@@ -123,6 +123,10 @@ impl Matcher {
         self.pending.clear();
     }
 
+    pub fn has_pending_keystrokes(&self) -> bool {
+        !self.pending.is_empty()
+    }
+
     pub fn push_keystroke(
         &mut self,
         keystroke: Keystroke,

crates/gpui/src/platform.rs 🔗

@@ -74,6 +74,7 @@ pub(crate) trait ForegroundPlatform {
 
     fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>);
     fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
+    fn on_will_open_menu(&self, callback: Box<dyn FnMut()>);
     fn set_menus(&self, menus: Vec<Menu>, matcher: &keymap::Matcher);
     fn prompt_for_paths(
         &self,

crates/gpui/src/platform/mac/platform.rs 🔗

@@ -93,6 +93,10 @@ unsafe fn build_classes() {
             sel!(validateMenuItem:),
             validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
         );
+        decl.add_method(
+            sel!(menuWillOpen:),
+            menu_will_open as extern "C" fn(&mut Object, Sel, id),
+        );
         decl.add_method(
             sel!(application:openURLs:),
             open_urls as extern "C" fn(&mut Object, Sel, id, id),
@@ -112,14 +116,21 @@ pub struct MacForegroundPlatformState {
     event: Option<Box<dyn FnMut(crate::Event) -> bool>>,
     menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
     validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
+    will_open_menu: Option<Box<dyn FnMut()>>,
     open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
     finish_launching: Option<Box<dyn FnOnce() -> ()>>,
     menu_actions: Vec<Box<dyn Action>>,
 }
 
 impl MacForegroundPlatform {
-    unsafe fn create_menu_bar(&self, menus: Vec<Menu>, keystroke_matcher: &keymap::Matcher) -> id {
+    unsafe fn create_menu_bar(
+        &self,
+        menus: Vec<Menu>,
+        delegate: id,
+        keystroke_matcher: &keymap::Matcher,
+    ) -> id {
         let menu_bar = NSMenu::new(nil).autorelease();
+        menu_bar.setDelegate_(delegate);
         let mut state = self.0.borrow_mut();
 
         state.menu_actions.clear();
@@ -130,6 +141,7 @@ impl MacForegroundPlatform {
             let menu_name = menu_config.name;
 
             menu.setTitle_(ns_string(menu_name));
+            menu.setDelegate_(delegate);
 
             for item_config in menu_config.items {
                 let item;
@@ -242,6 +254,10 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
         self.0.borrow_mut().menu_command = Some(callback);
     }
 
+    fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
+        self.0.borrow_mut().will_open_menu = Some(callback);
+    }
+
     fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
         self.0.borrow_mut().validate_menu_command = Some(callback);
     }
@@ -249,7 +265,7 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
     fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &keymap::Matcher) {
         unsafe {
             let app: id = msg_send![APP_CLASS, sharedApplication];
-            app.setMainMenu_(self.create_menu_bar(menus, keystroke_matcher));
+            app.setMainMenu_(self.create_menu_bar(menus, app.delegate(), keystroke_matcher));
         }
     }
 
@@ -764,6 +780,17 @@ extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
     }
 }
 
+extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
+    unsafe {
+        let platform = get_foreground_platform(this);
+        let mut platform = platform.0.borrow_mut();
+        if let Some(mut callback) = platform.will_open_menu.take() {
+            callback();
+            platform.will_open_menu = Some(callback);
+        }
+    }
+}
+
 unsafe fn ns_string(string: &str) -> id {
     NSString::alloc(nil).init_str(string).autorelease()
 }

crates/gpui/src/platform/test.rs 🔗

@@ -73,9 +73,8 @@ impl super::ForegroundPlatform for ForegroundPlatform {
     }
 
     fn on_menu_command(&self, _: Box<dyn FnMut(&dyn Action)>) {}
-
     fn on_validate_menu_command(&self, _: Box<dyn FnMut(&dyn Action) -> bool>) {}
-
+    fn on_will_open_menu(&self, _: Box<dyn FnMut()>) {}
     fn set_menus(&self, _: Vec<crate::Menu>, _: &keymap::Matcher) {}
 
     fn prompt_for_paths(