From a0e976599c19de4d6a3d5975c779b35151b365c5 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 16 Nov 2023 10:32:55 -0800 Subject: [PATCH 1/4] Salvage old distributed slice code --- Cargo.lock | 21 ++++++++++++ crates/gpui2/Cargo.toml | 1 + crates/gpui2/src/action.rs | 40 ++++++++++++++++++++++ crates/gpui2/src/gpui2.rs | 1 + crates/gpui2_macros/Cargo.toml | 2 +- crates/gpui2_macros/src/register_action.rs | 27 ++++++++++++--- 6 files changed, 86 insertions(+), 6 deletions(-) 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, + } } }; From 4de2c0f7ef5ce209f71c9f4afb71d2013d6c46d7 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 16 Nov 2023 17:32:02 -0800 Subject: [PATCH 2/4] Re-implement actions as derive macros instead of blanket impls --- crates/collab_ui2/src/collab_titlebar_item.rs | 6 +- crates/editor2/src/editor.rs | 28 +- crates/gpui2/src/action.rs | 264 +++++++----------- crates/gpui2/src/app.rs | 27 +- crates/gpui2/src/elements/div.rs | 10 +- crates/gpui2/src/gpui2.rs | 1 + crates/gpui2/src/key_dispatch.rs | 13 +- crates/gpui2/src/keymap/keymap.rs | 30 +- crates/gpui2/src/window.rs | 4 +- crates/gpui2/tests/action_macros.rs | 45 +++ crates/gpui2_macros/src/action.rs | 99 ++++--- crates/gpui2_macros/src/gpui2_macros.rs | 8 +- crates/gpui2_macros/src/register_action.rs | 55 +++- crates/live_kit_client2/examples/test_app.rs | 4 +- crates/settings2/src/keymap_file.rs | 4 +- crates/terminal_view2/src/terminal_view.rs | 8 +- crates/ui2/src/components/context_menu.rs | 5 +- crates/ui2/src/components/keybinding.rs | 5 +- crates/workspace2/src/pane.rs | 10 +- crates/workspace2/src/workspace2.rs | 13 +- crates/zed2/src/languages/json.rs | 2 +- crates/zed_actions2/src/lib.rs | 7 +- 22 files changed, 360 insertions(+), 288 deletions(-) create mode 100644 crates/gpui2/tests/action_macros.rs diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index c9d16c7a5d..a5a40c48b6 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -130,14 +130,12 @@ impl Render for CollabTitlebarItem { .color(Some(TextColor::Muted)), ) .tooltip(move |_, cx| { - // todo!() Replace with real action. - #[gpui::action] - struct NoAction {} cx.build_view(|_| { Tooltip::new("Recent Branches") .key_binding(KeyBinding::new(gpui::KeyBinding::new( "cmd-b", - NoAction {}, + // todo!() Replace with real action. + gpui::NoAction, None, ))) .meta("Only local branches shown") diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 8e7bd5876f..8f4b9ccf64 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -39,7 +39,7 @@ use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; use git::diff_hunk_to_display; use gpui::{ - action, actions, div, point, prelude::*, px, relative, rems, size, uniform_list, AnyElement, + actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context, EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, KeyContext, Model, MouseButton, ParentComponent, Pixels, Render, Styled, @@ -180,78 +180,78 @@ pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); // // .with_soft_wrap(true) // } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct SelectNext { #[serde(default)] pub replace_newest: bool, } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct SelectPrevious { #[serde(default)] pub replace_newest: bool, } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct SelectAllMatches { #[serde(default)] pub replace_newest: bool, } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct SelectToBeginningOfLine { #[serde(default)] stop_at_soft_wraps: bool, } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct MovePageUp { #[serde(default)] center_cursor: bool, } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct MovePageDown { #[serde(default)] center_cursor: bool, } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct SelectToEndOfLine { #[serde(default)] stop_at_soft_wraps: bool, } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct ToggleCodeActions { #[serde(default)] pub deployed_from_indicator: bool, } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct ConfirmCompletion { #[serde(default)] pub item_ix: Option, } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct ConfirmCodeAction { #[serde(default)] pub item_ix: Option, } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct ToggleComments { #[serde(default)] pub advance_downwards: bool, } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct FoldAt { pub buffer_row: u32, } -#[action] +#[derive(PartialEq, Clone, Deserialize, Action)] pub struct UnfoldAt { pub buffer_row: u32, } diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index 0a5ea781bd..8656f31a5e 100644 --- a/crates/gpui2/src/action.rs +++ b/crates/gpui2/src/action.rs @@ -1,10 +1,9 @@ use crate::SharedString; use anyhow::{anyhow, Context, Result}; use collections::HashMap; -use lazy_static::lazy_static; -use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; -use serde::Deserialize; -use std::any::{type_name, Any, TypeId}; +pub use no_action::NoAction; +use serde_json::json; +use std::any::{Any, TypeId}; /// Actions are used to implement keyboard-driven UI. /// When you declare an action, you can bind keys to the action in the keymap and @@ -15,24 +14,16 @@ use std::any::{type_name, Any, TypeId}; /// ```rust /// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline); /// ``` -/// More complex data types can also be actions. If you annotate your type with the `#[action]` proc macro, -/// it will automatically +/// More complex data types can also be actions. If you annotate your type with the action derive macro +/// it will be implemented and registered automatically. /// ``` -/// #[action] +/// #[derive(Clone, PartialEq, serde_derive::Deserialize, Action)] /// pub struct SelectNext { /// pub replace_newest: bool, /// } /// -/// Any type A that satisfies the following bounds is automatically an action: -/// -/// ``` -/// A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static, -/// ``` -/// -/// The `#[action]` annotation will derive these implementations for your struct automatically. If you -/// want to control them manually, you can use the lower-level `#[register_action]` macro, which only -/// generates the code needed to register your action before `main`. Then you'll need to implement all -/// the traits manually. +/// If you want to control the behavior of the action trait manually, you can use the lower-level `#[register_action]` +/// macro, which only generates the code needed to register your action before `main`. /// /// ``` /// #[gpui::register_action] @@ -41,77 +32,29 @@ use std::any::{type_name, Any, TypeId}; /// pub content: SharedString, /// } /// -/// impl std::default::Default for Paste { -/// fn default() -> Self { -/// Self { -/// content: SharedString::from("🍝"), -/// } -/// } +/// impl gpui::Action for Paste { +/// ///... /// } /// ``` -pub trait Action: std::fmt::Debug + 'static { - fn qualified_name() -> SharedString - where - Self: Sized; - fn build(value: Option) -> Result> - where - Self: Sized; - fn is_registered() -> bool - where - Self: Sized; - - fn partial_eq(&self, action: &dyn Action) -> bool; +pub trait Action: 'static { fn boxed_clone(&self) -> Box; fn as_any(&self) -> &dyn Any; + fn partial_eq(&self, action: &dyn Action) -> bool; + fn name(&self) -> &str; + + fn debug_name() -> &'static str + where + Self: Sized; + fn build(value: serde_json::Value) -> Result> + where + Self: Sized; } -// Types become actions by satisfying a list of trait bounds. -impl Action for A -where - A: for<'a> Deserialize<'a> + PartialEq + Default + Clone + std::fmt::Debug + 'static, -{ - fn qualified_name() -> SharedString { - let name = type_name::(); - let mut separator_matches = 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 - name[name_start_ix..].replace("2::", "::").into() - } - - fn build(params: Option) -> Result> - where - Self: Sized, - { - let action = if let Some(params) = params { - serde_json::from_value(params).context("failed to deserialize action")? - } else { - Self::default() - }; - Ok(Box::new(action)) - } - - fn is_registered() -> bool { - ACTION_REGISTRY - .read() - .names_by_type_id - .get(&TypeId::of::()) - .is_some() - } - - fn partial_eq(&self, action: &dyn Action) -> bool { - action - .as_any() - .downcast_ref::() - .map_or(false, |a| self == a) - } - - fn boxed_clone(&self) -> Box { - Box::new(self.clone()) - } - - fn as_any(&self) -> &dyn Any { - self +impl std::fmt::Debug for dyn Action { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("dyn Action") + .field("type_name", &self.name()) + .finish() } } @@ -119,69 +62,90 @@ impl dyn Action { pub fn type_id(&self) -> TypeId { self.as_any().type_id() } - - pub fn name(&self) -> SharedString { - ACTION_REGISTRY - .read() - .names_by_type_id - .get(&self.type_id()) - .expect("type is not a registered action") - .clone() - } } -type ActionBuilder = fn(json: Option) -> anyhow::Result>; +type ActionBuilder = fn(json: serde_json::Value) -> anyhow::Result>; -lazy_static! { - static ref ACTION_REGISTRY: RwLock = RwLock::default(); -} - -#[derive(Default)] -struct ActionRegistry { +pub(crate) struct ActionRegistry { builders_by_name: HashMap, names_by_type_id: HashMap, all_names: Vec, // So we can return a static slice. } -/// Register an action type to allow it to be referenced in keymaps. -pub fn register_action() { - let name = A::qualified_name(); - let mut lock = ACTION_REGISTRY.write(); - lock.builders_by_name.insert(name.clone(), A::build); - lock.names_by_type_id - .insert(TypeId::of::(), name.clone()); - lock.all_names.push(name); +impl Default for ActionRegistry { + fn default() -> Self { + let mut this = ActionRegistry { + builders_by_name: Default::default(), + names_by_type_id: Default::default(), + all_names: Default::default(), + }; + + this.load_actions(); + + this + } } -/// Construct an action based on its name and optional JSON parameters sourced from the keymap. -pub fn build_action_from_type(type_id: &TypeId) -> Result> { - let lock = ACTION_REGISTRY.read(); - let name = lock - .names_by_type_id - .get(type_id) - .ok_or_else(|| anyhow!("no action type registered for {:?}", type_id))? - .clone(); - drop(lock); +/// 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; - build_action(&name, None) +/// 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 type_id: TypeId, + pub build: ActionBuilder, } -/// 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_REGISTRY.read(); +/// 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]; - let build_action = lock - .builders_by_name - .get(name) - .ok_or_else(|| anyhow!("no action type registered for {}", name))?; - (build_action)(params) -} +impl ActionRegistry { + /// Load all registered actions into the registry. + pub(crate) fn load_actions(&mut self) { + for builder in __GPUI_ACTIONS { + let action = builder(); + let name: SharedString = qualify_action(action.name).into(); + self.builders_by_name.insert(name.clone(), action.build); + self.names_by_type_id.insert(action.type_id, name.clone()); + self.all_names.push(name); + } + } -pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> { - let lock = ACTION_REGISTRY.read(); - RwLockReadGuard::map(lock, |registry: &ActionRegistry| { - registry.all_names.as_slice() - }) + /// Construct an action based on its name and optional JSON parameters sourced from the keymap. + pub fn build_action_type(&self, type_id: &TypeId) -> Result> { + let name = self + .names_by_type_id + .get(type_id) + .ok_or_else(|| anyhow!("no action type registered for {:?}", type_id))? + .clone(); + + self.build_action(&name, None) + } + + /// Construct an action based on its name and optional JSON parameters sourced from the keymap. + pub fn build_action( + &self, + name: &str, + params: Option, + ) -> Result> { + let build_action = self + .builders_by_name + .get(name) + .ok_or_else(|| anyhow!("no action type registered for {}", name))?; + (build_action)(params.unwrap_or_else(|| json!({}))) + .with_context(|| format!("Attempting to build action {}", name)) + } + + pub fn all_action_names(&self) -> &[SharedString] { + self.all_names.as_slice() + } } /// Defines unit structs that can be used as actions. @@ -191,7 +155,7 @@ macro_rules! actions { () => {}; ( $name:ident ) => { - #[gpui::action] + #[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::serde_derive::Deserialize, gpui::Action)] pub struct $name; }; @@ -201,42 +165,20 @@ macro_rules! actions { }; } -/// 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. +/// This used by our macros to pre-process the action name deterministically #[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 { +pub fn qualify_action(action_name: &'static str) -> String { 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() + action_name[name_start_ix..] + .replace("2::", "::") + .to_string() } -pub(crate) fn load_actions_2() { - let mut lock = ACTION_REGISTRY.write(); +mod no_action { + use crate as gpui; - 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); - } + actions!(NoAction); } diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index c76b62b510..b5083b97c2 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -14,12 +14,13 @@ use smallvec::SmallVec; pub use test_context::*; use crate::{ - current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle, - AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, - Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, - LayoutId, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet, - Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, - Window, WindowContext, WindowHandle, WindowId, + current_platform, image_cache::ImageCache, Action, ActionRegistry, AnyBox, AnyView, + AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, + DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, + ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform, + PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, + TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext, + WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -182,6 +183,7 @@ pub struct AppContext { text_system: Arc, flushing_effects: bool, pending_updates: usize, + pub(crate) actions: Rc, pub(crate) active_drag: Option, pub(crate) active_tooltip: Option, pub(crate) next_frame_callbacks: HashMap>, @@ -240,6 +242,7 @@ impl AppContext { platform: platform.clone(), app_metadata, text_system, + actions: Rc::new(ActionRegistry::default()), flushing_effects: false, pending_updates: 0, active_drag: None, @@ -964,6 +967,18 @@ impl AppContext { pub fn propagate(&mut self) { self.propagate_event = true; } + + pub fn build_action( + &self, + name: &str, + data: Option, + ) -> Result> { + self.actions.build_action(name, data) + } + + pub fn all_action_names(&self) -> &[SharedString] { + self.actions.all_action_names() + } } impl Context for AppContext { diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 31a8827109..ebbc34a48a 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -237,11 +237,11 @@ pub trait InteractiveComponent: Sized + Element { // // if we are relying on this side-effect still, removing the debug_assert! // likely breaks the command_palette tests. - debug_assert!( - A::is_registered(), - "{:?} is not registered as an action", - A::qualified_name() - ); + // debug_assert!( + // A::is_registered(), + // "{:?} is not registered as an action", + // A::qualified_name() + // ); self.interactivity().action_listeners.push(( TypeId::of::(), Box::new(move |view, action, phase, cx| { diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index c2d10154de..88ecd52c03 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -55,6 +55,7 @@ use private::Sealed; pub use refineable::*; pub use scene::*; pub use serde; +pub use serde_derive; pub use serde_json; pub use smallvec; pub use smol::Timer; diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index 962a030844..5fbf83bfba 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -1,6 +1,6 @@ use crate::{ - build_action_from_type, Action, DispatchPhase, FocusId, KeyBinding, KeyContext, KeyMatch, - Keymap, Keystroke, KeystrokeMatcher, WindowContext, + Action, ActionRegistry, DispatchPhase, FocusId, KeyBinding, KeyContext, KeyMatch, Keymap, + Keystroke, KeystrokeMatcher, WindowContext, }; use collections::HashMap; use parking_lot::Mutex; @@ -10,7 +10,6 @@ use std::{ rc::Rc, sync::Arc, }; -use util::ResultExt; #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub struct DispatchNodeId(usize); @@ -22,6 +21,7 @@ pub(crate) struct DispatchTree { focusable_node_ids: HashMap, keystroke_matchers: HashMap, KeystrokeMatcher>, keymap: Arc>, + action_registry: Rc, } #[derive(Default)] @@ -41,7 +41,7 @@ pub(crate) struct DispatchActionListener { } impl DispatchTree { - pub fn new(keymap: Arc>) -> Self { + pub fn new(keymap: Arc>, action_registry: Rc) -> Self { Self { node_stack: Vec::new(), context_stack: Vec::new(), @@ -49,6 +49,7 @@ impl DispatchTree { focusable_node_ids: HashMap::default(), keystroke_matchers: HashMap::default(), keymap, + action_registry, } } @@ -153,7 +154,9 @@ impl DispatchTree { for node_id in self.dispatch_path(*node) { let node = &self.nodes[node_id.0]; for DispatchActionListener { action_type, .. } in &node.action_listeners { - actions.extend(build_action_from_type(action_type).log_err()); + // Intentionally silence these errors without logging. + // If an action cannot be built by default, it's not available. + actions.extend(self.action_registry.build_action_type(action_type).ok()); } } } diff --git a/crates/gpui2/src/keymap/keymap.rs b/crates/gpui2/src/keymap/keymap.rs index 989ee7a8d5..8152693c07 100644 --- a/crates/gpui2/src/keymap/keymap.rs +++ b/crates/gpui2/src/keymap/keymap.rs @@ -1,7 +1,10 @@ -use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke}; +use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke, NoAction}; use collections::HashSet; use smallvec::SmallVec; -use std::{any::TypeId, collections::HashMap}; +use std::{ + any::{Any, TypeId}, + collections::HashMap, +}; #[derive(Copy, Clone, Eq, PartialEq, Default)] pub struct KeymapVersion(usize); @@ -37,20 +40,19 @@ impl Keymap { } pub fn add_bindings>(&mut self, bindings: T) { - // todo!("no action") - // let no_action_id = (NoAction {}).id(); + let no_action_id = &(NoAction {}).type_id(); let mut new_bindings = Vec::new(); - let has_new_disabled_keystrokes = false; + let mut has_new_disabled_keystrokes = false; for binding in bindings { - // if binding.action().id() == no_action_id { - // has_new_disabled_keystrokes |= self - // .disabled_keystrokes - // .entry(binding.keystrokes) - // .or_default() - // .insert(binding.context_predicate); - // } else { - new_bindings.push(binding); - // } + if binding.action.type_id() == *no_action_id { + has_new_disabled_keystrokes |= self + .disabled_keystrokes + .entry(binding.keystrokes) + .or_default() + .insert(binding.context_predicate); + } else { + new_bindings.push(binding); + } } if has_new_disabled_keystrokes { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index b0d9d07df2..ff4c13abce 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -311,8 +311,8 @@ impl Window { layout_engine: TaffyLayoutEngine::new(), root_view: None, element_id_stack: GlobalElementId::default(), - previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone())), - current_frame: Frame::new(DispatchTree::new(cx.keymap.clone())), + previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), + current_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), default_prevented: true, diff --git a/crates/gpui2/tests/action_macros.rs b/crates/gpui2/tests/action_macros.rs new file mode 100644 index 0000000000..49064ffd86 --- /dev/null +++ b/crates/gpui2/tests/action_macros.rs @@ -0,0 +1,45 @@ +use serde_derive::Deserialize; + +#[test] +fn test_derive() { + use gpui2 as gpui; + + #[derive(PartialEq, Clone, Deserialize, gpui2_macros::Action)] + struct AnotherTestAction; + + #[gpui2_macros::register_action] + #[derive(PartialEq, Clone, gpui::serde_derive::Deserialize)] + struct RegisterableAction {} + + impl gpui::Action for RegisterableAction { + fn boxed_clone(&self) -> Box { + todo!() + } + + fn as_any(&self) -> &dyn std::any::Any { + todo!() + } + + fn partial_eq(&self, _action: &dyn gpui::Action) -> bool { + todo!() + } + + fn name(&self) -> &str { + todo!() + } + + fn debug_name() -> &'static str + where + Self: Sized, + { + todo!() + } + + fn build(_value: serde_json::Value) -> anyhow::Result> + where + Self: Sized, + { + todo!() + } + } +} diff --git a/crates/gpui2_macros/src/action.rs b/crates/gpui2_macros/src/action.rs index 564f35d6a4..abc75a8759 100644 --- a/crates/gpui2_macros/src/action.rs +++ b/crates/gpui2_macros/src/action.rs @@ -15,48 +15,81 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, DeriveInput}; +use syn::{parse_macro_input, DeriveInput, Error}; + +use crate::register_action::register_action; + +pub fn action(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); -pub fn action(_attr: TokenStream, item: TokenStream) -> TokenStream { - let input = parse_macro_input!(item as DeriveInput); let name = &input.ident; - let attrs = input - .attrs - .into_iter() - .filter(|attr| !attr.path.is_ident("action")) - .collect::>(); - let attributes = quote! { - #[gpui::register_action] - #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)] - #(#attrs)* + if input.generics.lt_token.is_some() { + return Error::new(name.span(), "Actions must be a concrete type") + .into_compile_error() + .into(); + } + + let is_unit_struct = match input.data { + syn::Data::Struct(struct_data) => struct_data.fields.is_empty(), + syn::Data::Enum(_) => false, + syn::Data::Union(_) => false, }; - let visibility = input.vis; - let output = match input.data { - syn::Data::Struct(ref struct_data) => match &struct_data.fields { - syn::Fields::Named(_) | syn::Fields::Unnamed(_) => { - let fields = &struct_data.fields; - quote! { - #attributes - #visibility struct #name #fields - } + let build_impl = if is_unit_struct { + quote! { + Ok(std::boxed::Box::new(Self {})) + } + } else { + quote! { + Ok(std::boxed::Box::new(gpui::serde_json::from_value::(value)?)) + } + }; + + let register_action = register_action(&name); + + let output = quote! { + const _: fn() = || { + fn assert_impl gpui::serde::Deserialize<'a> + ::std::cmp::PartialEq + ::std::clone::Clone>() {} + assert_impl::<#name>(); + }; + + impl gpui::Action for #name { + fn name(&self) -> &'static str + { + ::std::any::type_name::<#name>() } - syn::Fields::Unit => { - quote! { - #attributes - #visibility struct #name; - } + + fn debug_name() -> &'static str + where + Self: ::std::marker::Sized + { + ::std::any::type_name::<#name>() } - }, - syn::Data::Enum(ref enum_data) => { - let variants = &enum_data.variants; - quote! { - #attributes - #visibility enum #name { #variants } + + fn build(value: gpui::serde_json::Value) -> gpui::Result<::std::boxed::Box> + where + Self: ::std::marker::Sized { + #build_impl + } + + fn partial_eq(&self, action: &dyn gpui::Action) -> bool { + action + .as_any() + .downcast_ref::() + .map_or(false, |a| self == a) + } + + fn boxed_clone(&self) -> std::boxed::Box { + ::std::boxed::Box::new(self.clone()) + } + + fn as_any(&self) -> &dyn ::std::any::Any { + self } } - _ => panic!("Expected a struct or an enum."), + + #register_action }; TokenStream::from(output) diff --git a/crates/gpui2_macros/src/gpui2_macros.rs b/crates/gpui2_macros/src/gpui2_macros.rs index 80b67e1a12..3ce8373689 100644 --- a/crates/gpui2_macros/src/gpui2_macros.rs +++ b/crates/gpui2_macros/src/gpui2_macros.rs @@ -11,14 +11,14 @@ pub fn style_helpers(args: TokenStream) -> TokenStream { style_helpers::style_helpers(args) } -#[proc_macro_attribute] -pub fn action(attr: TokenStream, item: TokenStream) -> TokenStream { - action::action(attr, item) +#[proc_macro_derive(Action)] +pub fn action(input: TokenStream) -> TokenStream { + action::action(input) } #[proc_macro_attribute] pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream { - register_action::register_action(attr, item) + register_action::register_action_macro(attr, item) } #[proc_macro_derive(Component, attributes(component))] diff --git a/crates/gpui2_macros/src/register_action.rs b/crates/gpui2_macros/src/register_action.rs index e6c47e8c52..3d398c873c 100644 --- a/crates/gpui2_macros/src/register_action.rs +++ b/crates/gpui2_macros/src/register_action.rs @@ -12,13 +12,54 @@ // gpui2::register_action_builder::() // } use proc_macro::TokenStream; +use proc_macro2::Ident; use quote::{format_ident, quote}; -use syn::{parse_macro_input, DeriveInput}; +use syn::{parse_macro_input, DeriveInput, Error}; -pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream { +pub fn register_action_macro(_attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as DeriveInput); - let type_name = &input.ident; + let registration = register_action(&input.ident); + let has_action_derive = input + .attrs + .iter() + .find(|attr| { + (|| { + let meta = attr.parse_meta().ok()?; + meta.path().is_ident("derive").then(|| match meta { + syn::Meta::Path(_) => None, + syn::Meta::NameValue(_) => None, + syn::Meta::List(list) => list + .nested + .iter() + .find(|list| match list { + syn::NestedMeta::Meta(meta) => meta.path().is_ident("Action"), + syn::NestedMeta::Lit(_) => false, + }) + .map(|_| true), + })? + })() + .unwrap_or(false) + }) + .is_some(); + + if has_action_derive { + return Error::new( + input.ident.span(), + "The Action derive macro has already registered this action", + ) + .into_compile_error() + .into(); + } + + TokenStream::from(quote! { + #input + + #registration + }) +} + +pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream { let static_slice_name = format_ident!("__GPUI_ACTIONS_{}", type_name.to_string().to_uppercase()); @@ -27,9 +68,7 @@ pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream { type_name.to_string().to_lowercase() ); - let expanded = quote! { - #input - + quote! { #[doc(hidden)] #[gpui::linkme::distributed_slice(gpui::__GPUI_ACTIONS)] #[linkme(crate = gpui::linkme)] @@ -44,7 +83,5 @@ pub fn register_action(_attr: TokenStream, item: TokenStream) -> TokenStream { build: <#type_name as gpui::Action>::build, } } - }; - - TokenStream::from(expanded) + } } diff --git a/crates/live_kit_client2/examples/test_app.rs b/crates/live_kit_client2/examples/test_app.rs index 0b9e54f9b0..00aec53baf 100644 --- a/crates/live_kit_client2/examples/test_app.rs +++ b/crates/live_kit_client2/examples/test_app.rs @@ -1,7 +1,7 @@ use std::{sync::Arc, time::Duration}; use futures::StreamExt; -use gpui::KeyBinding; +use gpui::{Action, KeyBinding}; use live_kit_client2::{ LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, }; @@ -10,7 +10,7 @@ use log::LevelFilter; use serde_derive::Deserialize; use simplelog::SimpleLogger; -#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Action)] struct Quit; fn main() { diff --git a/crates/settings2/src/keymap_file.rs b/crates/settings2/src/keymap_file.rs index 9f279864ee..93635935cb 100644 --- a/crates/settings2/src/keymap_file.rs +++ b/crates/settings2/src/keymap_file.rs @@ -73,9 +73,9 @@ impl KeymapFile { "Expected first item in array to be a string." ))); }; - gpui::build_action(&name, Some(data)) + cx.build_action(&name, Some(data)) } - Value::String(name) => gpui::build_action(&name, None), + Value::String(name) => cx.build_action(&name, None), Value::Null => Ok(no_action()), _ => { return Some(Err(anyhow!("Expected two-element array, got {action:?}"))) diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index ad33716384..f815dbe0ea 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -9,7 +9,7 @@ pub mod terminal_panel; // use crate::terminal_element::TerminalElement; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{ - actions, div, img, red, register_action, AnyElement, AppContext, Component, DispatchPhase, Div, + actions, div, img, red, Action, AnyElement, AppContext, Component, DispatchPhase, Div, EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView, InputHandler, InteractiveComponent, KeyDownEvent, Keystroke, Model, MouseButton, ParentComponent, Pixels, Render, SharedString, Styled, Task, View, ViewContext, VisualContext, @@ -55,12 +55,10 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); #[derive(Clone, Debug, PartialEq)] pub struct ScrollTerminal(pub i32); -#[register_action] -#[derive(Clone, Debug, Default, Deserialize, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Action)] pub struct SendText(String); -#[register_action] -#[derive(Clone, Debug, Default, Deserialize, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Action)] pub struct SendKeystroke(String); actions!(Clear, Copy, Paste, ShowCharacterPalette, SearchTest); diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index 8f32c3ed56..6b3371e338 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -84,7 +84,8 @@ pub use stories::*; mod stories { use super::*; use crate::story::Story; - use gpui::{action, Div, Render}; + use gpui::{Div, Render}; + use serde::Deserialize; pub struct ContextMenuStory; @@ -92,7 +93,7 @@ mod stories { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - #[action] + #[derive(PartialEq, Clone, Deserialize, gpui::Action)] struct PrintCurrentDate {} Story::container(cx) diff --git a/crates/ui2/src/components/keybinding.rs b/crates/ui2/src/components/keybinding.rs index 04e036f365..8da5273bf5 100644 --- a/crates/ui2/src/components/keybinding.rs +++ b/crates/ui2/src/components/keybinding.rs @@ -81,13 +81,12 @@ pub use stories::*; mod stories { use super::*; use crate::Story; - use gpui::{action, Div, Render}; + use gpui::{actions, Div, Render}; use itertools::Itertools; pub struct KeybindingStory; - #[action] - struct NoAction {} + actions!(NoAction); pub fn binding(key: &str) -> gpui::KeyBinding { gpui::KeyBinding::new(key, NoAction {}, None) diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index dcc8a4a14f..b86240f419 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -7,7 +7,7 @@ use crate::{ use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use gpui::{ - actions, prelude::*, register_action, AppContext, AsyncWindowContext, Component, Div, EntityId, + actions, prelude::*, Action, AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, FocusHandle, Focusable, FocusableView, Model, PromptLevel, Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext, }; @@ -70,15 +70,13 @@ pub struct ActivateItem(pub usize); // pub pane: WeakView, // } -#[register_action] -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, Default, Action)] #[serde(rename_all = "camelCase")] pub struct CloseActiveItem { pub save_intent: Option, } -#[register_action] -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, Default, Action)] #[serde(rename_all = "camelCase")] pub struct CloseAllItems { pub save_intent: Option, @@ -1917,7 +1915,7 @@ impl Render for Pane { .on_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)) .on_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)) .size_full() - .on_action(|pane: &mut Self, action, cx| { + .on_action(|pane: &mut Self, action: &CloseActiveItem, cx| { pane.close_active_item(action, cx) .map(|task| task.detach_and_log_err(cx)); }) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index a146ad1822..ab7a69e75c 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -29,11 +29,11 @@ use futures::{ Future, FutureExt, StreamExt, }; use gpui::{ - actions, div, point, register_action, size, Action, AnyModel, AnyView, AnyWeakView, AppContext, - AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, - FocusHandle, FocusableView, GlobalPixels, InteractiveComponent, KeyContext, Model, - ModelContext, ParentComponent, Point, Render, Size, Styled, Subscription, Task, View, - ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, + actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext, + AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, + FocusableView, GlobalPixels, InteractiveComponent, KeyContext, Model, ModelContext, + ParentComponent, Point, Render, Size, Styled, Subscription, Task, View, ViewContext, + VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; @@ -194,8 +194,7 @@ impl Clone for Toast { } } -#[register_action] -#[derive(Debug, Default, Clone, Deserialize, PartialEq)] +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Action)] pub struct OpenTerminal { pub working_directory: PathBuf, } diff --git a/crates/zed2/src/languages/json.rs b/crates/zed2/src/languages/json.rs index cf9b33d968..f04f59cf6d 100644 --- a/crates/zed2/src/languages/json.rs +++ b/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 = gpui::all_action_names(); + let action_names = cx.all_action_names(); let staff_mode = cx.is_staff(); let language_names = &self.languages.language_names(); let settings_schema = cx.global::().json_schema( diff --git a/crates/zed_actions2/src/lib.rs b/crates/zed_actions2/src/lib.rs index 7f0c19853e..456d1f5973 100644 --- a/crates/zed_actions2/src/lib.rs +++ b/crates/zed_actions2/src/lib.rs @@ -1,4 +1,5 @@ -use gpui::action; +use gpui::Action; +use serde::Deserialize; // If the zed binary doesn't use anything in this crate, it will be optimized away // and the actions won't initialize. So we just provide an empty initialization function @@ -9,12 +10,12 @@ use gpui::action; // https://github.com/mmastrac/rust-ctor/issues/280 pub fn init() {} -#[action] +#[derive(Clone, PartialEq, Deserialize, Action)] pub struct OpenBrowser { pub url: String, } -#[action] +#[derive(Clone, PartialEq, Deserialize, Action)] pub struct OpenZedURL { pub url: String, } From 49d3e1cc4bebc0e66b1c995aa7b38456e2f0a6e9 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 16 Nov 2023 17:39:05 -0800 Subject: [PATCH 3/4] Add default derive --- crates/editor2/src/editor.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 8f4b9ccf64..8d394270a7 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -180,78 +180,78 @@ pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); // // .with_soft_wrap(true) // } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct SelectNext { #[serde(default)] pub replace_newest: bool, } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct SelectPrevious { #[serde(default)] pub replace_newest: bool, } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct SelectAllMatches { #[serde(default)] pub replace_newest: bool, } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct SelectToBeginningOfLine { #[serde(default)] stop_at_soft_wraps: bool, } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct MovePageUp { #[serde(default)] center_cursor: bool, } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct MovePageDown { #[serde(default)] center_cursor: bool, } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct SelectToEndOfLine { #[serde(default)] stop_at_soft_wraps: bool, } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct ToggleCodeActions { #[serde(default)] pub deployed_from_indicator: bool, } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct ConfirmCompletion { #[serde(default)] pub item_ix: Option, } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct ConfirmCodeAction { #[serde(default)] pub item_ix: Option, } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct ToggleComments { #[serde(default)] pub advance_downwards: bool, } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct FoldAt { pub buffer_row: u32, } -#[derive(PartialEq, Clone, Deserialize, Action)] +#[derive(PartialEq, Clone, Deserialize, Default, Action)] pub struct UnfoldAt { pub buffer_row: u32, } From 432572c592c4d735a218be0e04d5757641bde97f Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 16 Nov 2023 18:04:35 -0800 Subject: [PATCH 4/4] #RemoveThe2 --- crates/command_palette2/src/command_palette.rs | 2 +- crates/gpui2/src/action.rs | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index 31eb608ef5..6264606ed9 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -47,7 +47,7 @@ impl CommandPalette { .available_actions() .into_iter() .filter_map(|action| { - let name = action.name(); + let name = gpui::remove_the_2(action.name()); let namespace = name.split("::").next().unwrap_or("malformed action name"); if filter.is_some_and(|f| f.filtered_namespaces.contains(namespace)) { return None; diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index 8656f31a5e..958eaabdb8 100644 --- a/crates/gpui2/src/action.rs +++ b/crates/gpui2/src/action.rs @@ -3,7 +3,10 @@ use anyhow::{anyhow, Context, Result}; use collections::HashMap; pub use no_action::NoAction; use serde_json::json; -use std::any::{Any, TypeId}; +use std::{ + any::{Any, TypeId}, + ops::Deref, +}; /// Actions are used to implement keyboard-driven UI. /// When you declare an action, you can bind keys to the action in the keymap and @@ -111,7 +114,8 @@ impl ActionRegistry { pub(crate) fn load_actions(&mut self) { for builder in __GPUI_ACTIONS { let action = builder(); - let name: SharedString = qualify_action(action.name).into(); + //todo(remove) + let name: SharedString = remove_the_2(action.name).into(); self.builders_by_name.insert(name.clone(), action.build); self.names_by_type_id.insert(action.type_id, name.clone()); self.all_names.push(name); @@ -135,9 +139,11 @@ impl ActionRegistry { name: &str, params: Option, ) -> Result> { + //todo(remove) + let name = remove_the_2(name); let build_action = self .builders_by_name - .get(name) + .get(name.deref()) .ok_or_else(|| anyhow!("no action type registered for {}", name))?; (build_action)(params.unwrap_or_else(|| json!({}))) .with_context(|| format!("Attempting to build action {}", name)) @@ -165,9 +171,8 @@ macro_rules! actions { }; } -/// This used by our macros to pre-process the action name deterministically -#[doc(hidden)] -pub fn qualify_action(action_name: &'static str) -> String { +//todo!(remove) +pub fn remove_the_2(action_name: &str) -> String { 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);