From 80630cd4d900275ad626f41517898b617e79ee12 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 7 Nov 2023 20:23:02 -0700 Subject: [PATCH] WIP --- 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(+) create mode 100644 crates/gpui2_macros/src/action.rs create mode 100644 crates/gpui2_macros/src/register_action.rs diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index 90f312f502ca60d011c7c40c1dac60a1a5427bef..289d858c62a8d246fba18561f606dcf36ec26eac 100644 --- a/crates/gpui2/src/action.rs +++ b/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) -> anyhow::Result>; + +#[ctor] +static ACTION_BUILDERS: Mutex> = Mutex::default(); + +/// Register an action type to allow it to be referenced in keymaps. +pub fn register_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) -> Result> { + 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] diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 348da17356dd3f48d1a507cfcb0c8d19a4413d2b..4441c9565fe7af5668816e6137b7aa0151439592 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/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::*; diff --git a/crates/gpui2_macros/src/action.rs b/crates/gpui2_macros/src/action.rs new file mode 100644 index 0000000000000000000000000000000000000000..9a8ec0d8b09d82d85a9faa1d6664a44482612822 --- /dev/null +++ b/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) +} diff --git a/crates/gpui2_macros/src/gpui2_macros.rs b/crates/gpui2_macros/src/gpui2_macros.rs index 2e0c0547f79e29f1e3bcc5bfcc43cfed516f7df9..d1271ad041cce5ffe4f8e758e394c9710cefd6b1 100644 --- a/crates/gpui2_macros/src/gpui2_macros.rs +++ b/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) diff --git a/crates/gpui2_macros/src/register_action.rs b/crates/gpui2_macros/src/register_action.rs new file mode 100644 index 0000000000000000000000000000000000000000..77516cdec8278edc28e011800795f17294e5c2ee --- /dev/null +++ b/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::() +// } +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) +}