Add AppContext::dispatch_action and use it for app menu actions

Nathan Sobo , Marshall , and Julia created

Co-Authored-By: Marshall <marshall@zed.dev>
Co-Authored-By: Julia <julia@zed.dev>

Change summary

crates/gpui2/src/app.rs                    | 61 +++++++++++++++++++++++
crates/gpui2/src/platform.rs               |  6 +-
crates/gpui2/src/platform/app_menu.rs      | 27 +---------
crates/gpui2/src/platform/mac/platform.rs  |  6 +-
crates/gpui2/src/platform/test/platform.rs |  6 +-
5 files changed, 73 insertions(+), 33 deletions(-)

Detailed changes

crates/gpui2/src/app.rs 🔗

@@ -39,7 +39,10 @@ use std::{
     sync::{atomic::Ordering::SeqCst, Arc},
     time::Duration,
 };
-use util::http::{self, HttpClient};
+use util::{
+    http::{self, HttpClient},
+    ResultExt,
+};
 
 /// Temporary(?) wrapper around RefCell<AppContext> to help us debug any double borrows.
 /// Strongly consider removing after stabilization.
@@ -1055,6 +1058,62 @@ impl AppContext {
         self.global_action_listeners
             .contains_key(&action.as_any().type_id())
     }
+
+    pub fn dispatch_action(&mut self, action: &dyn Action) {
+        self.propagate_event = true;
+
+        if let Some(mut global_listeners) = self
+            .global_action_listeners
+            .remove(&action.as_any().type_id())
+        {
+            for listener in &global_listeners {
+                listener(action, DispatchPhase::Capture, self);
+                if !self.propagate_event {
+                    break;
+                }
+            }
+
+            global_listeners.extend(
+                self.global_action_listeners
+                    .remove(&action.as_any().type_id())
+                    .unwrap_or_default(),
+            );
+
+            self.global_action_listeners
+                .insert(action.as_any().type_id(), global_listeners);
+        }
+
+        if self.propagate_event {
+            if let Some(active_window) = self.active_window() {
+                active_window
+                    .update(self, |_, cx| cx.dispatch_action(action.boxed_clone()))
+                    .log_err();
+            }
+        }
+
+        if self.propagate_event {
+            if let Some(mut global_listeners) = self
+                .global_action_listeners
+                .remove(&action.as_any().type_id())
+            {
+                for listener in global_listeners.iter().rev() {
+                    listener(action, DispatchPhase::Bubble, self);
+                    if !self.propagate_event {
+                        break;
+                    }
+                }
+
+                global_listeners.extend(
+                    self.global_action_listeners
+                        .remove(&action.as_any().type_id())
+                        .unwrap_or_default(),
+                );
+
+                self.global_action_listeners
+                    .insert(action.as_any().type_id(), global_listeners);
+            }
+        }
+    }
 }
 
 impl Context for AppContext {

crates/gpui2/src/platform.rs 🔗

@@ -92,9 +92,9 @@ pub trait Platform: 'static {
     fn on_reopen(&self, callback: Box<dyn FnMut()>);
     fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
 
-    fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>);
-    fn on_will_open_menu(&self, callback: Box<dyn FnMut()>);
-    fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
+    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
+    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
+    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
 
     fn os_name(&self) -> &'static str;
     fn os_version(&self) -> Result<SemanticVersion>;

crates/gpui2/src/platform/app_menu.rs 🔗

@@ -53,14 +53,14 @@ pub enum OsAction {
 }
 
 pub(crate) fn init(platform: &dyn Platform, cx: &mut AppContext) {
-    platform.on_will_open_menu(Box::new({
+    platform.on_will_open_app_menu(Box::new({
         let cx = cx.to_async();
         move || {
             cx.update(|cx| cx.clear_pending_keystrokes()).ok();
         }
     }));
 
-    platform.on_validate_menu_command(Box::new({
+    platform.on_validate_app_menu_command(Box::new({
         let cx = cx.to_async();
         move |action| {
             cx.update(|cx| cx.is_action_available(action))
@@ -68,29 +68,10 @@ pub(crate) fn init(platform: &dyn Platform, cx: &mut AppContext) {
         }
     }));
 
-    platform.on_menu_command(Box::new({
+    platform.on_app_menu_action(Box::new({
         let cx = cx.to_async();
         move |action| {
-            cx.update(|cx| {
-                // if let Some(main_window) = cx.active_window() {
-                //     let dispatched = main_window
-                //         .update(&mut *cx, |cx| {
-                //             if let Some(view_id) = cx.focused_view_id() {
-                //                 cx.dispatch_action(Some(view_id), action);
-                //                 true
-                //             } else {
-                //                 false
-                //             }
-                //         })
-                //         .unwrap_or(false);
-
-                //     if dispatched {
-                //         return;
-                //     }
-                // }
-                // cx.dispatch_global_action_any(action);
-            })
-            .log_err();
+            cx.update(|cx| cx.dispatch_action(action)).log_err();
         }
     }));
 }

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

@@ -683,15 +683,15 @@ impl Platform for MacPlatform {
         }
     }
 
-    fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
+    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
         self.0.lock().menu_command = Some(callback);
     }
 
-    fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
+    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
         self.0.lock().will_open_menu = Some(callback);
     }
 
-    fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
+    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
         self.0.lock().validate_menu_command = Some(callback);
     }
 

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

@@ -205,15 +205,15 @@ impl Platform for TestPlatform {
         unimplemented!()
     }
 
-    fn on_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action)>) {
+    fn on_app_menu_action(&self, _callback: Box<dyn FnMut(&dyn crate::Action)>) {
         unimplemented!()
     }
 
-    fn on_will_open_menu(&self, _callback: Box<dyn FnMut()>) {
+    fn on_will_open_app_menu(&self, _callback: Box<dyn FnMut()>) {
         unimplemented!()
     }
 
-    fn on_validate_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {
+    fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {
         unimplemented!()
     }