Register actions globally before main

Nathan Sobo created

Change summary

crates/client2/src/client2.rs              |   3 
crates/editor2/src/editor.rs               | 124 ------------------------
crates/gpui2/src/action.rs                 |  30 ++++-
crates/gpui2/src/app.rs                    |  31 -----
crates/gpui2_macros/src/register_action.rs |   6 
crates/settings2/src/keymap_file.rs        |   4 
crates/storybook2/src/stories/focus.rs     |   2 
crates/zed2/src/languages/json.rs          |   2 
8 files changed, 33 insertions(+), 169 deletions(-)

Detailed changes

crates/client2/src/client2.rs 🔗

@@ -80,7 +80,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
     init_settings(cx);
 
     let client = Arc::downgrade(client);
-    cx.register_action_type::<SignIn>();
     cx.on_action({
         let client = client.clone();
         move |_: &SignIn, cx| {
@@ -93,7 +92,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
         }
     });
 
-    cx.register_action_type::<SignOut>();
     cx.on_action({
         let client = client.clone();
         move |_: &SignOut, cx| {
@@ -106,7 +104,6 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
         }
     });
 
-    cx.register_action_type::<Reconnect>();
     cx.on_action({
         let client = client.clone();
         move |_: &Reconnect, cx| {

crates/editor2/src/editor.rs 🔗

@@ -406,130 +406,6 @@ pub fn init_settings(cx: &mut AppContext) {
 
 pub fn init(cx: &mut AppContext) {
     init_settings(cx);
-    // cx.register_action_type(Editor::new_file);
-    // cx.register_action_type(Editor::new_file_in_direction);
-    // cx.register_action_type(Editor::cancel);
-    // cx.register_action_type(Editor::newline);
-    // cx.register_action_type(Editor::newline_above);
-    // cx.register_action_type(Editor::newline_below);
-    // cx.register_action_type(Editor::backspace);
-    // cx.register_action_type(Editor::delete);
-    // cx.register_action_type(Editor::tab);
-    // cx.register_action_type(Editor::tab_prev);
-    // cx.register_action_type(Editor::indent);
-    // cx.register_action_type(Editor::outdent);
-    // cx.register_action_type(Editor::delete_line);
-    // cx.register_action_type(Editor::join_lines);
-    // cx.register_action_type(Editor::sort_lines_case_sensitive);
-    // cx.register_action_type(Editor::sort_lines_case_insensitive);
-    // cx.register_action_type(Editor::reverse_lines);
-    // cx.register_action_type(Editor::shuffle_lines);
-    // cx.register_action_type(Editor::convert_to_upper_case);
-    // cx.register_action_type(Editor::convert_to_lower_case);
-    // cx.register_action_type(Editor::convert_to_title_case);
-    // cx.register_action_type(Editor::convert_to_snake_case);
-    // cx.register_action_type(Editor::convert_to_kebab_case);
-    // cx.register_action_type(Editor::convert_to_upper_camel_case);
-    // cx.register_action_type(Editor::convert_to_lower_camel_case);
-    // cx.register_action_type(Editor::delete_to_previous_word_start);
-    // cx.register_action_type(Editor::delete_to_previous_subword_start);
-    // cx.register_action_type(Editor::delete_to_next_word_end);
-    // cx.register_action_type(Editor::delete_to_next_subword_end);
-    // cx.register_action_type(Editor::delete_to_beginning_of_line);
-    // cx.register_action_type(Editor::delete_to_end_of_line);
-    // cx.register_action_type(Editor::cut_to_end_of_line);
-    // cx.register_action_type(Editor::duplicate_line);
-    // cx.register_action_type(Editor::move_line_up);
-    // cx.register_action_type(Editor::move_line_down);
-    // cx.register_action_type(Editor::transpose);
-    // cx.register_action_type(Editor::cut);
-    // cx.register_action_type(Editor::copy);
-    // cx.register_action_type(Editor::paste);
-    // cx.register_action_type(Editor::undo);
-    // cx.register_action_type(Editor::redo);
-    cx.register_action_type::<MoveUp>();
-    // cx.register_action_type(Editor::move_page_up);
-    cx.register_action_type::<MoveDown>();
-    // cx.register_action_type(Editor::move_page_down);
-    // cx.register_action_type(Editor::next_screen);
-    cx.register_action_type::<MoveLeft>();
-    cx.register_action_type::<MoveRight>();
-    // cx.register_action_type(Editor::move_to_previous_word_start);
-    // cx.register_action_type(Editor::move_to_previous_subword_start);
-    // cx.register_action_type(Editor::move_to_next_word_end);
-    // cx.register_action_type(Editor::move_to_next_subword_end);
-    // cx.register_action_type(Editor::move_to_beginning_of_line);
-    // cx.register_action_type(Editor::move_to_end_of_line);
-    // cx.register_action_type(Editor::move_to_start_of_paragraph);
-    // cx.register_action_type(Editor::move_to_end_of_paragraph);
-    // cx.register_action_type(Editor::move_to_beginning);
-    // cx.register_action_type(Editor::move_to_end);
-    // cx.register_action_type(Editor::select_up);
-    // cx.register_action_type(Editor::select_down);
-    // cx.register_action_type(Editor::select_left);
-    // cx.register_action_type(Editor::select_right);
-    // cx.register_action_type(Editor::select_to_previous_word_start);
-    // cx.register_action_type(Editor::select_to_previous_subword_start);
-    // cx.register_action_type(Editor::select_to_next_word_end);
-    // cx.register_action_type(Editor::select_to_next_subword_end);
-    // cx.register_action_type(Editor::select_to_beginning_of_line);
-    // cx.register_action_type(Editor::select_to_end_of_line);
-    // cx.register_action_type(Editor::select_to_start_of_paragraph);
-    // cx.register_action_type(Editor::select_to_end_of_paragraph);
-    // cx.register_action_type(Editor::select_to_beginning);
-    // cx.register_action_type(Editor::select_to_end);
-    // cx.register_action_type(Editor::select_all);
-    // cx.register_action_type(Editor::select_all_matches);
-    // cx.register_action_type(Editor::select_line);
-    // cx.register_action_type(Editor::split_selection_into_lines);
-    // cx.register_action_type(Editor::add_selection_above);
-    // cx.register_action_type(Editor::add_selection_below);
-    // cx.register_action_type(Editor::select_next);
-    // cx.register_action_type(Editor::select_previous);
-    // cx.register_action_type(Editor::toggle_comments);
-    // cx.register_action_type(Editor::select_larger_syntax_node);
-    // cx.register_action_type(Editor::select_smaller_syntax_node);
-    // cx.register_action_type(Editor::move_to_enclosing_bracket);
-    // cx.register_action_type(Editor::undo_selection);
-    // cx.register_action_type(Editor::redo_selection);
-    // cx.register_action_type(Editor::go_to_diagnostic);
-    // cx.register_action_type(Editor::go_to_prev_diagnostic);
-    // cx.register_action_type(Editor::go_to_hunk);
-    // cx.register_action_type(Editor::go_to_prev_hunk);
-    // cx.register_action_type(Editor::go_to_definition);
-    // cx.register_action_type(Editor::go_to_definition_split);
-    // cx.register_action_type(Editor::go_to_type_definition);
-    // cx.register_action_type(Editor::go_to_type_definition_split);
-    // cx.register_action_type(Editor::fold);
-    // cx.register_action_type(Editor::fold_at);
-    // cx.register_action_type(Editor::unfold_lines);
-    // cx.register_action_type(Editor::unfold_at);
-    // cx.register_action_type(Editor::gutter_hover);
-    // cx.register_action_type(Editor::fold_selected_ranges);
-    // cx.register_action_type(Editor::show_completions);
-    // cx.register_action_type(Editor::toggle_code_actions);
-    // cx.register_action_type(Editor::open_excerpts);
-    // cx.register_action_type(Editor::toggle_soft_wrap);
-    // cx.register_action_type(Editor::toggle_inlay_hints);
-    // cx.register_action_type(Editor::reveal_in_finder);
-    // cx.register_action_type(Editor::copy_path);
-    // cx.register_action_type(Editor::copy_relative_path);
-    // cx.register_action_type(Editor::copy_highlight_json);
-    // cx.add_async_action(Editor::format);
-    // cx.register_action_type(Editor::restart_language_server);
-    // cx.register_action_type(Editor::show_character_palette);
-    // cx.add_async_action(Editor::confirm_completion);
-    // cx.add_async_action(Editor::confirm_code_action);
-    // cx.add_async_action(Editor::rename);
-    // cx.add_async_action(Editor::confirm_rename);
-    // cx.add_async_action(Editor::find_all_references);
-    // cx.register_action_type(Editor::next_copilot_suggestion);
-    // cx.register_action_type(Editor::previous_copilot_suggestion);
-    // cx.register_action_type(Editor::copilot_suggest);
-    // cx.register_action_type(Editor::context_menu_first);
-    // cx.register_action_type(Editor::context_menu_prev);
-    // cx.register_action_type(Editor::context_menu_next);
-    // cx.register_action_type(Editor::context_menu_last);
 
     hover_popover::init(cx);
     scroll::actions::init(cx);

crates/gpui2/src/action.rs 🔗

@@ -1,8 +1,8 @@
 use crate::SharedString;
 use anyhow::{anyhow, Context, Result};
 use collections::{HashMap, HashSet};
-use ctor::ctor;
-use parking_lot::Mutex;
+use lazy_static::lazy_static;
+use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
 use serde::Deserialize;
 use std::any::{type_name, Any};
 
@@ -21,23 +21,41 @@ pub trait Action: std::fmt::Debug + 'static {
 
 type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
 
-#[ctor]
-static ACTION_BUILDERS: Mutex<HashMap<SharedString, ActionBuilder>> = Mutex::default();
+lazy_static! {
+    static ref ACTION_REGISTRY: RwLock<ActionRegistry> = RwLock::default();
+}
+
+#[derive(Default)]
+struct ActionRegistry {
+    builders_by_name: HashMap<SharedString, ActionBuilder>,
+    all_names: Vec<SharedString>, // So we can return a static slice.
+}
 
 /// Register an action type to allow it to be referenced in keymaps.
 pub fn register_action<A: Action>() {
-    ACTION_BUILDERS.lock().insert(A::qualified_name(), A::build);
+    let name = A::qualified_name();
+    let mut lock = ACTION_REGISTRY.write();
+    lock.builders_by_name.insert(name.clone(), A::build);
+    lock.all_names.push(name);
 }
 
 /// Construct an action based on its name and optional JSON parameters sourced from the keymap.
 pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
-    let lock = &ACTION_BUILDERS.lock();
+    let lock = ACTION_REGISTRY.read();
     let build_action = lock
+        .builders_by_name
         .get(name)
         .ok_or_else(|| anyhow!("no action type registered for {}", name))?;
     (build_action)(params)
 }
 
+pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> {
+    let lock = ACTION_REGISTRY.read();
+    RwLockReadGuard::map(lock, |registry: &ActionRegistry| {
+        registry.all_names.as_slice()
+    })
+}
+
 // actions defines structs that can be used as actions.
 #[macro_export]
 macro_rules! actions {

crates/gpui2/src/app.rs 🔗

@@ -17,9 +17,9 @@ use crate::{
     current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
     AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
     Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId,
-    PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SharedString,
-    SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem,
-    View, Window, WindowContext, WindowHandle, WindowId,
+    PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet,
+    Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
+    WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet, VecDeque};
@@ -140,7 +140,6 @@ impl App {
     }
 }
 
-type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
 pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
 type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
 type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
@@ -176,7 +175,6 @@ pub struct AppContext {
     pub(crate) keymap: Arc<Mutex<Keymap>>,
     pub(crate) global_action_listeners:
         HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self)>>>,
-    action_builders: HashMap<SharedString, ActionBuilder>,
     pending_effects: VecDeque<Effect>,
     pub(crate) pending_notifications: HashSet<EntityId>,
     pub(crate) pending_global_notifications: HashSet<TypeId>,
@@ -234,7 +232,6 @@ impl AppContext {
                 windows: SlotMap::with_key(),
                 keymap: Arc::new(Mutex::new(Keymap::default())),
                 global_action_listeners: HashMap::default(),
-                action_builders: HashMap::default(),
                 pending_effects: VecDeque::new(),
                 pending_notifications: HashSet::default(),
                 pending_global_notifications: HashSet::default(),
@@ -695,10 +692,6 @@ impl AppContext {
         )
     }
 
-    pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = SharedString> + 'a {
-        self.action_builders.keys().cloned()
-    }
-
     /// Move the global of the given type to the stack.
     pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
         GlobalLease::new(
@@ -761,24 +754,6 @@ impl AppContext {
             }));
     }
 
-    /// Register an action type to allow it to be referenced in keymaps.
-    pub fn register_action_type<A: Action>(&mut self) {
-        self.action_builders.insert(A::qualified_name(), A::build);
-    }
-
-    /// Construct an action based on its name and parameters.
-    pub fn build_action(
-        &mut self,
-        name: &str,
-        params: Option<serde_json::Value>,
-    ) -> Result<Box<dyn Action>> {
-        let build = self
-            .action_builders
-            .get(name)
-            .ok_or_else(|| anyhow!("no action type registered for {}", name))?;
-        (build)(params)
-    }
-
     /// Event handlers propagate events by default. Call this method to stop dispatching to
     /// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
     /// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by

crates/gpui2_macros/src/register_action.rs 🔗

@@ -1,14 +1,14 @@
 // Input:
 //
-// struct Foo {}
+// struct FooBar {}
 
 // Output:
 //
-// struct Foo {}
+// struct FooBar {}
 //
 // #[allow(non_snake_case)]
 // #[gpui2::ctor]
-// fn register_Foo_builder() {
+// fn register_foobar_builder() {
 //     gpui2::register_action_builder::<Foo>()
 // }
 use proc_macro::TokenStream;

crates/settings2/src/keymap_file.rs 🔗

@@ -73,9 +73,9 @@ impl KeymapFile {
                                     "Expected first item in array to be a string."
                                 )));
                             };
-                            cx.build_action(&name, Some(data))
+                            gpui::build_action(&name, Some(data))
                         }
-                        Value::String(name) => cx.build_action(&name, None),
+                        Value::String(name) => gpui::build_action(&name, None),
                         Value::Null => Ok(no_action()),
                         _ => {
                             return Some(Err(anyhow!("Expected two-element array, got {action:?}")))

crates/storybook2/src/stories/focus.rs 🔗

@@ -15,8 +15,6 @@ impl FocusStory {
             KeyBinding::new("cmd-a", ActionB, Some("child-1")),
             KeyBinding::new("cmd-c", ActionC, None),
         ]);
-        cx.register_action_type::<ActionA>();
-        cx.register_action_type::<ActionB>();
 
         cx.build_view(move |cx| Self {})
     }

crates/zed2/src/languages/json.rs 🔗

@@ -107,7 +107,7 @@ impl LspAdapter for JsonLspAdapter {
         &self,
         cx: &mut AppContext,
     ) -> BoxFuture<'static, serde_json::Value> {
-        let action_names = cx.all_action_names().collect::<Vec<_>>();
+        let action_names = gpui::all_action_names();
         let staff_mode = cx.is_staff();
         let language_names = &self.languages.language_names();
         let settings_schema = cx.global::<SettingsStore>().json_schema(