WIP

Nathan Sobo created

Change summary

crates/gpui2/src/action.rs                 | 24 ++++++++++++++++++
crates/gpui2/src/gpui2.rs                  |  1 
crates/gpui2_macros/src/action.rs          | 30 ++++++++++++++++++++++
crates/gpui2_macros/src/gpui2_macros.rs    |  6 ++++
crates/gpui2_macros/src/register_action.rs | 32 ++++++++++++++++++++++++
5 files changed, 93 insertions(+)

Detailed changes

crates/gpui2/src/action.rs 🔗

@@ -1,6 +1,8 @@
 use crate::SharedString;
 use anyhow::{anyhow, Context, Result};
 use collections::{HashMap, HashSet};
+use ctor::ctor;
+use parking_lot::Mutex;
 use serde::Deserialize;
 use std::any::{type_name, Any};
 
@@ -17,17 +19,38 @@ pub trait Action: std::fmt::Debug + 'static {
     fn as_any(&self) -> &dyn Any;
 }
 
+type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
+
+#[ctor]
+static ACTION_BUILDERS: Mutex<HashMap<SharedString, ActionBuilder>> = Mutex::default();
+
+/// 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);
+}
+
+/// 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 build_action = lock
+        .get(name)
+        .ok_or_else(|| anyhow!("no action type registered for {}", name))?;
+    (build_action)(params)
+}
+
 // actions defines structs that can be used as actions.
 #[macro_export]
 macro_rules! actions {
     () => {};
 
     ( $name:ident ) => {
+        #[gpui::register_action]
         #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
         pub struct $name;
     };
 
     ( $name:ident { $($token:tt)* } ) => {
+        #[gpui::register_action]
         #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
         pub struct $name { $($token)* }
     };
@@ -317,6 +340,7 @@ fn skip_whitespace(source: &str) -> &str {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate as gpui;
     use DispatchContextPredicate::*;
 
     #[test]

crates/gpui2/src/gpui2.rs 🔗

@@ -36,6 +36,7 @@ pub use anyhow::Result;
 pub use app::*;
 pub use assets::*;
 pub use color::*;
+pub use ctor::ctor;
 pub use element::*;
 pub use elements::*;
 pub use executor::*;

crates/gpui2_macros/src/action.rs 🔗

@@ -0,0 +1,30 @@
+// Input:
+//
+// #[action]
+// struct Foo {}
+
+// Output:
+//
+// #[gpui::register_action]
+// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)]
+// struct Foo {}
+
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, DeriveInput};
+
+pub fn action(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(item as DeriveInput);
+    let name = &input.ident;
+    let data = &input.data;
+
+    let data_tokens = quote! { #data }.into();
+
+    let expanded = quote! {
+        #[gpui::register_action]
+        #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)]
+        struct #name { #data }
+    };
+
+    TokenStream::from(expanded)
+}

crates/gpui2_macros/src/gpui2_macros.rs 🔗

@@ -1,6 +1,7 @@
 use proc_macro::TokenStream;
 
 mod derive_component;
+mod register_action;
 mod style_helpers;
 mod test;
 
@@ -9,6 +10,11 @@ pub fn style_helpers(args: TokenStream) -> TokenStream {
     style_helpers::style_helpers(args)
 }
 
+#[proc_macro_attribute]
+pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream {
+    register_action::register_action(attr, item)
+}
+
 #[proc_macro_derive(Component, attributes(component))]
 pub fn derive_component(input: TokenStream) -> TokenStream {
     derive_component::derive_component(input)

crates/gpui2_macros/src/register_action.rs 🔗

@@ -0,0 +1,32 @@
+// Input:
+//
+// struct Foo {}
+
+// Output:
+//
+// struct Foo {}
+//
+// #[allow(non_snake_case)]
+// #[gpui2::ctor]
+// fn register_Foo_builder() {
+//     gpui2::register_action_builder::<Foo>()
+// }
+use proc_macro::TokenStream;
+use quote::{format_ident, quote};
+use syn::{parse_macro_input, DeriveInput};
+
+pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(item as DeriveInput);
+    let type_name = &input.ident;
+    let ctor_fn_name = format_ident!("register_{}_builder", type_name);
+
+    let expanded = quote! {
+        #input
+        #[allow(non_snake_case)]
+        #[gpui::ctor]
+        fn #ctor_fn_name() {
+            gpui::register_action::<#type_name>()
+        }
+    };
+    TokenStream::from(expanded)
+}