Derive application menu key equivalents from the keymap

Max Brunsfeld created

Change summary

assets/keymaps/default.json              |  3 +
crates/gpui/src/app.rs                   |  4 +-
crates/gpui/src/platform.rs              |  3 +
crates/gpui/src/platform/mac/platform.rs | 32 ++++++++++++-------------
crates/gpui/src/platform/test.rs         |  4 +-
crates/zed/src/menus.rs                  | 11 --------
6 files changed, 23 insertions(+), 34 deletions(-)

Detailed changes

assets/keymaps/default.json πŸ”—

@@ -18,7 +18,8 @@
             "cmd-s": "workspace::Save",
             "cmd-=": "zed::IncreaseBufferFontSize",
             "cmd--": "zed::DecreaseBufferFontSize",
-            "cmd-,": "zed::OpenSettings"
+            "cmd-,": "zed::OpenSettings",
+            "cmd-q": "zed::Quit"
         }
     },
     {

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

@@ -154,7 +154,6 @@ pub struct Menu<'a> {
 pub enum MenuItem<'a> {
     Action {
         name: &'a str,
-        keystroke: Option<&'a str>,
         action: Box<dyn Action>,
     },
     Separator,
@@ -1070,7 +1069,8 @@ impl MutableAppContext {
     }
 
     pub fn set_menus(&mut self, menus: Vec<Menu>) {
-        self.foreground_platform.set_menus(menus);
+        self.foreground_platform
+            .set_menus(menus, &self.keystroke_matcher);
     }
 
     fn prompt(

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

@@ -14,6 +14,7 @@ use crate::{
         rect::{RectF, RectI},
         vector::Vector2F,
     },
+    keymap,
     text_layout::{LineLayout, RunStyle},
     Action, ClipboardItem, Menu, Scene,
 };
@@ -72,7 +73,7 @@ pub(crate) trait ForegroundPlatform {
     fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>);
 
     fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>);
-    fn set_menus(&self, menus: Vec<Menu>);
+    fn set_menus(&self, menus: Vec<Menu>, matcher: &keymap::Matcher);
     fn prompt_for_paths(
         &self,
         options: PathPromptOptions,

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

@@ -1,7 +1,6 @@
 use super::{BoolExt as _, Dispatcher, FontSystem, Window};
 use crate::{
-    executor,
-    keymap::Keystroke,
+    executor, keymap,
     platform::{self, CursorStyle},
     Action, ClipboardItem, Event, Menu, MenuItem,
 };
@@ -114,7 +113,7 @@ pub struct MacForegroundPlatformState {
 }
 
 impl MacForegroundPlatform {
-    unsafe fn create_menu_bar(&self, menus: Vec<Menu>) -> id {
+    unsafe fn create_menu_bar(&self, menus: Vec<Menu>, keystroke_matcher: &keymap::Matcher) -> id {
         let menu_bar = NSMenu::new(nil).autorelease();
         let mut state = self.0.borrow_mut();
 
@@ -134,19 +133,18 @@ impl MacForegroundPlatform {
                     MenuItem::Separator => {
                         item = NSMenuItem::separatorItem(nil);
                     }
-                    MenuItem::Action {
-                        name,
-                        keystroke,
-                        action,
-                    } => {
-                        if let Some(keystroke) = keystroke {
-                            let keystroke = Keystroke::parse(keystroke).unwrap_or_else(|err| {
-                                panic!(
-                                    "Invalid keystroke for menu item {}:{} - {:?}",
-                                    menu_name, name, err
-                                )
-                            });
+                    MenuItem::Action { name, action } => {
+                        let mut keystroke = None;
+                        if let Some(binding) = keystroke_matcher
+                            .bindings_for_action_type(action.as_any().type_id())
+                            .next()
+                        {
+                            if binding.keystrokes().len() == 1 {
+                                keystroke = binding.keystrokes().first()
+                            }
+                        }
 
+                        if let Some(keystroke) = keystroke {
                             let mut mask = NSEventModifierFlags::empty();
                             for (modifier, flag) in &[
                                 (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
@@ -239,10 +237,10 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
         self.0.borrow_mut().menu_command = Some(callback);
     }
 
-    fn set_menus(&self, menus: Vec<Menu>) {
+    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));
+            app.setMainMenu_(self.create_menu_bar(menus, keystroke_matcher));
         }
     }
 

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

@@ -1,7 +1,7 @@
 use super::{AppVersion, CursorStyle, WindowBounds};
 use crate::{
     geometry::vector::{vec2f, Vector2F},
-    Action, ClipboardItem,
+    keymap, Action, ClipboardItem,
 };
 use anyhow::{anyhow, Result};
 use parking_lot::Mutex;
@@ -74,7 +74,7 @@ impl super::ForegroundPlatform for ForegroundPlatform {
 
     fn on_menu_command(&self, _: Box<dyn FnMut(&dyn Action)>) {}
 
-    fn set_menus(&self, _: Vec<crate::Menu>) {}
+    fn set_menus(&self, _: Vec<crate::Menu>, _: &keymap::Matcher) {}
 
     fn prompt_for_paths(
         &self,

crates/zed/src/menus.rs πŸ”—

@@ -10,24 +10,20 @@ pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
             items: vec![
                 MenuItem::Action {
                     name: "About Zed…",
-                    keystroke: None,
                     action: Box::new(super::About),
                 },
                 MenuItem::Action {
                     name: "Check for Updates",
-                    keystroke: None,
                     action: Box::new(auto_update::Check),
                 },
                 MenuItem::Separator,
                 MenuItem::Action {
                     name: "Install CLI",
-                    keystroke: None,
                     action: Box::new(super::InstallCommandLineInterface),
                 },
                 MenuItem::Separator,
                 MenuItem::Action {
                     name: "Quit",
-                    keystroke: Some("cmd-q"),
                     action: Box::new(super::Quit),
                 },
             ],
@@ -37,13 +33,11 @@ pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
             items: vec![
                 MenuItem::Action {
                     name: "New",
-                    keystroke: Some("cmd-n"),
                     action: Box::new(workspace::OpenNew(state.clone())),
                 },
                 MenuItem::Separator,
                 MenuItem::Action {
                     name: "Open…",
-                    keystroke: Some("cmd-o"),
                     action: Box::new(workspace::Open(state.clone())),
                 },
             ],
@@ -53,28 +47,23 @@ pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
             items: vec![
                 MenuItem::Action {
                     name: "Undo",
-                    keystroke: Some("cmd-z"),
                     action: Box::new(editor::Undo),
                 },
                 MenuItem::Action {
                     name: "Redo",
-                    keystroke: Some("cmd-Z"),
                     action: Box::new(editor::Redo),
                 },
                 MenuItem::Separator,
                 MenuItem::Action {
                     name: "Cut",
-                    keystroke: Some("cmd-x"),
                     action: Box::new(editor::Cut),
                 },
                 MenuItem::Action {
                     name: "Copy",
-                    keystroke: Some("cmd-c"),
                     action: Box::new(editor::Copy),
                 },
                 MenuItem::Action {
                     name: "Paste",
-                    keystroke: Some("cmd-v"),
                     action: Box::new(editor::Paste),
                 },
             ],