diff --git a/Cargo.lock b/Cargo.lock index bf2e964ea8..3b45a32918 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3797,6 +3797,7 @@ dependencies = [ "image", "itertools 0.10.5", "lazy_static", + "linkme", "log", "media", "metal", @@ -4815,6 +4816,26 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linkme" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ed2ee9464ff9707af8e9ad834cffa4802f072caad90639c583dd3c62e6e608" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba125974b109d512fccbc6c0244e7580143e460895dfd6ea7f8bbb692fd94396" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "linux-raw-sys" version = "0.0.42" diff --git a/crates/gpui2/Cargo.toml b/crates/gpui2/Cargo.toml index df461af7b8..1bec9d43dc 100644 --- a/crates/gpui2/Cargo.toml +++ b/crates/gpui2/Cargo.toml @@ -22,6 +22,7 @@ sqlez = { path = "../sqlez" } async-task = "4.0.3" backtrace = { version = "0.3", optional = true } ctor.workspace = true +linkme = "0.3" derive_more.workspace = true dhat = { version = "0.3", optional = true } env_logger = { version = "0.9", optional = true } diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index a81bcfcdbc..0a5ea781bd 100644 --- a/crates/gpui2/src/action.rs +++ b/crates/gpui2/src/action.rs @@ -200,3 +200,43 @@ macro_rules! actions { actions!($($rest)*); }; } + +/// This type must be public so that our macros can build it in other crates. +/// But this is an implementation detail and should not be used directly. +#[doc(hidden)] +pub struct ActionData { + pub name: &'static str, + pub build: ActionBuilder, + pub type_id: TypeId, +} + +/// This type must be public so that our macros can build it in other crates. +/// But this is an implementation detail and should not be used directly. +#[doc(hidden)] +pub type MacroActionBuilder = fn() -> ActionData; + +/// This constant must be public to be accessible from other crates. +/// But it's existence is an implementation detail and should not be used directly. +#[doc(hidden)] +#[linkme::distributed_slice] +pub static __GPUI_ACTIONS: [MacroActionBuilder]; + +fn qualify_name(action_name: &'static str) -> SharedString { + let mut separator_matches = action_name.rmatch_indices("::"); + separator_matches.next().unwrap(); + let name_start_ix = separator_matches.next().map_or(0, |(ix, _)| ix + 2); + // todo!() remove the 2 replacement when migration is done + action_name[name_start_ix..].replace("2::", "::").into() +} + +pub(crate) fn load_actions_2() { + let mut lock = ACTION_REGISTRY.write(); + + for action in __GPUI_ACTIONS { + let action = action(); + let name = qualify_name(action.name); + lock.builders_by_name.insert(name.clone(), action.build); + lock.names_by_type_id.insert(action.type_id, name.clone()); + lock.all_names.push(name); + } +} diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 3b98b846c4..c2d10154de 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -49,6 +49,7 @@ pub use input::*; pub use interactive::*; pub use key_dispatch::*; pub use keymap::*; +pub use linkme; pub use platform::*; use private::Sealed; pub use refineable::*; diff --git a/crates/gpui2_macros/Cargo.toml b/crates/gpui2_macros/Cargo.toml index eb44334095..aab669c1b7 100644 --- a/crates/gpui2_macros/Cargo.toml +++ b/crates/gpui2_macros/Cargo.toml @@ -9,6 +9,6 @@ path = "src/gpui2_macros.rs" proc-macro = true [dependencies] -syn = "1.0.72" +syn = { version = "1.0.72", features = ["full"] } quote = "1.0.9" proc-macro2 = "1.0.66" diff --git a/crates/gpui2_macros/src/register_action.rs b/crates/gpui2_macros/src/register_action.rs index 68c39ad9bd..e6c47e8c52 100644 --- a/crates/gpui2_macros/src/register_action.rs +++ b/crates/gpui2_macros/src/register_action.rs @@ -18,14 +18,31 @@ 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.to_string().to_lowercase()); + + let static_slice_name = + format_ident!("__GPUI_ACTIONS_{}", type_name.to_string().to_uppercase()); + + let action_builder_fn_name = format_ident!( + "__gpui_actions_builder_{}", + type_name.to_string().to_lowercase() + ); let expanded = quote! { #input - #[allow(non_snake_case)] - #[gpui::ctor] - fn #ctor_fn_name() { - gpui::register_action::<#type_name>() + + #[doc(hidden)] + #[gpui::linkme::distributed_slice(gpui::__GPUI_ACTIONS)] + #[linkme(crate = gpui::linkme)] + static #static_slice_name: gpui::MacroActionBuilder = #action_builder_fn_name; + + /// This is an auto generated function, do not use. + #[doc(hidden)] + fn #action_builder_fn_name() -> gpui::ActionData { + gpui::ActionData { + name: ::std::any::type_name::<#type_name>(), + type_id: ::std::any::TypeId::of::<#type_name>(), + build: <#type_name as gpui::Action>::build, + } } };