Detailed changes
@@ -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]
@@ -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::*;
@@ -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)
+}
@@ -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)
@@ -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)
+}