From 80630cd4d900275ad626f41517898b617e79ee12 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 7 Nov 2023 20:23:02 -0700 Subject: [PATCH 1/5] 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) +} From 814e62050c89d340fe63a2e2033b1c455d7a97b3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 7 Nov 2023 20:58:37 -0700 Subject: [PATCH 2/5] Register actions globally before main --- crates/client2/src/client2.rs | 3 - crates/editor2/src/editor.rs | 124 --------------------- crates/gpui2/src/action.rs | 30 ++++- crates/gpui2/src/app.rs | 31 +----- crates/gpui2_macros/src/register_action.rs | 6 +- crates/settings2/src/keymap_file.rs | 4 +- crates/storybook2/src/stories/focus.rs | 2 - crates/zed2/src/languages/json.rs | 2 +- 8 files changed, 33 insertions(+), 169 deletions(-) diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index 4a1c5f321c375a097a89252830bd17c572d0c648..93ec7f329bfab51f11d3689904cbc4d5edb62071 100644 --- a/crates/client2/src/client2.rs +++ b/crates/client2/src/client2.rs @@ -80,7 +80,6 @@ pub fn init(client: &Arc, cx: &mut AppContext) { init_settings(cx); let client = Arc::downgrade(client); - cx.register_action_type::(); cx.on_action({ let client = client.clone(); move |_: &SignIn, cx| { @@ -93,7 +92,6 @@ pub fn init(client: &Arc, cx: &mut AppContext) { } }); - cx.register_action_type::(); cx.on_action({ let client = client.clone(); move |_: &SignOut, cx| { @@ -106,7 +104,6 @@ pub fn init(client: &Arc, cx: &mut AppContext) { } }); - cx.register_action_type::(); cx.on_action({ let client = client.clone(); move |_: &Reconnect, cx| { diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 13afe8aebac141f9d202ab363cdae0ed0cb62f79..e7936b8ebc226a569775df417362f03818964753 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -406,130 +406,6 @@ pub fn init_settings(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) { init_settings(cx); - // cx.register_action_type(Editor::new_file); - // cx.register_action_type(Editor::new_file_in_direction); - // cx.register_action_type(Editor::cancel); - // cx.register_action_type(Editor::newline); - // cx.register_action_type(Editor::newline_above); - // cx.register_action_type(Editor::newline_below); - // cx.register_action_type(Editor::backspace); - // cx.register_action_type(Editor::delete); - // cx.register_action_type(Editor::tab); - // cx.register_action_type(Editor::tab_prev); - // cx.register_action_type(Editor::indent); - // cx.register_action_type(Editor::outdent); - // cx.register_action_type(Editor::delete_line); - // cx.register_action_type(Editor::join_lines); - // cx.register_action_type(Editor::sort_lines_case_sensitive); - // cx.register_action_type(Editor::sort_lines_case_insensitive); - // cx.register_action_type(Editor::reverse_lines); - // cx.register_action_type(Editor::shuffle_lines); - // cx.register_action_type(Editor::convert_to_upper_case); - // cx.register_action_type(Editor::convert_to_lower_case); - // cx.register_action_type(Editor::convert_to_title_case); - // cx.register_action_type(Editor::convert_to_snake_case); - // cx.register_action_type(Editor::convert_to_kebab_case); - // cx.register_action_type(Editor::convert_to_upper_camel_case); - // cx.register_action_type(Editor::convert_to_lower_camel_case); - // cx.register_action_type(Editor::delete_to_previous_word_start); - // cx.register_action_type(Editor::delete_to_previous_subword_start); - // cx.register_action_type(Editor::delete_to_next_word_end); - // cx.register_action_type(Editor::delete_to_next_subword_end); - // cx.register_action_type(Editor::delete_to_beginning_of_line); - // cx.register_action_type(Editor::delete_to_end_of_line); - // cx.register_action_type(Editor::cut_to_end_of_line); - // cx.register_action_type(Editor::duplicate_line); - // cx.register_action_type(Editor::move_line_up); - // cx.register_action_type(Editor::move_line_down); - // cx.register_action_type(Editor::transpose); - // cx.register_action_type(Editor::cut); - // cx.register_action_type(Editor::copy); - // cx.register_action_type(Editor::paste); - // cx.register_action_type(Editor::undo); - // cx.register_action_type(Editor::redo); - cx.register_action_type::(); - // cx.register_action_type(Editor::move_page_up); - cx.register_action_type::(); - // cx.register_action_type(Editor::move_page_down); - // cx.register_action_type(Editor::next_screen); - cx.register_action_type::(); - cx.register_action_type::(); - // cx.register_action_type(Editor::move_to_previous_word_start); - // cx.register_action_type(Editor::move_to_previous_subword_start); - // cx.register_action_type(Editor::move_to_next_word_end); - // cx.register_action_type(Editor::move_to_next_subword_end); - // cx.register_action_type(Editor::move_to_beginning_of_line); - // cx.register_action_type(Editor::move_to_end_of_line); - // cx.register_action_type(Editor::move_to_start_of_paragraph); - // cx.register_action_type(Editor::move_to_end_of_paragraph); - // cx.register_action_type(Editor::move_to_beginning); - // cx.register_action_type(Editor::move_to_end); - // cx.register_action_type(Editor::select_up); - // cx.register_action_type(Editor::select_down); - // cx.register_action_type(Editor::select_left); - // cx.register_action_type(Editor::select_right); - // cx.register_action_type(Editor::select_to_previous_word_start); - // cx.register_action_type(Editor::select_to_previous_subword_start); - // cx.register_action_type(Editor::select_to_next_word_end); - // cx.register_action_type(Editor::select_to_next_subword_end); - // cx.register_action_type(Editor::select_to_beginning_of_line); - // cx.register_action_type(Editor::select_to_end_of_line); - // cx.register_action_type(Editor::select_to_start_of_paragraph); - // cx.register_action_type(Editor::select_to_end_of_paragraph); - // cx.register_action_type(Editor::select_to_beginning); - // cx.register_action_type(Editor::select_to_end); - // cx.register_action_type(Editor::select_all); - // cx.register_action_type(Editor::select_all_matches); - // cx.register_action_type(Editor::select_line); - // cx.register_action_type(Editor::split_selection_into_lines); - // cx.register_action_type(Editor::add_selection_above); - // cx.register_action_type(Editor::add_selection_below); - // cx.register_action_type(Editor::select_next); - // cx.register_action_type(Editor::select_previous); - // cx.register_action_type(Editor::toggle_comments); - // cx.register_action_type(Editor::select_larger_syntax_node); - // cx.register_action_type(Editor::select_smaller_syntax_node); - // cx.register_action_type(Editor::move_to_enclosing_bracket); - // cx.register_action_type(Editor::undo_selection); - // cx.register_action_type(Editor::redo_selection); - // cx.register_action_type(Editor::go_to_diagnostic); - // cx.register_action_type(Editor::go_to_prev_diagnostic); - // cx.register_action_type(Editor::go_to_hunk); - // cx.register_action_type(Editor::go_to_prev_hunk); - // cx.register_action_type(Editor::go_to_definition); - // cx.register_action_type(Editor::go_to_definition_split); - // cx.register_action_type(Editor::go_to_type_definition); - // cx.register_action_type(Editor::go_to_type_definition_split); - // cx.register_action_type(Editor::fold); - // cx.register_action_type(Editor::fold_at); - // cx.register_action_type(Editor::unfold_lines); - // cx.register_action_type(Editor::unfold_at); - // cx.register_action_type(Editor::gutter_hover); - // cx.register_action_type(Editor::fold_selected_ranges); - // cx.register_action_type(Editor::show_completions); - // cx.register_action_type(Editor::toggle_code_actions); - // cx.register_action_type(Editor::open_excerpts); - // cx.register_action_type(Editor::toggle_soft_wrap); - // cx.register_action_type(Editor::toggle_inlay_hints); - // cx.register_action_type(Editor::reveal_in_finder); - // cx.register_action_type(Editor::copy_path); - // cx.register_action_type(Editor::copy_relative_path); - // cx.register_action_type(Editor::copy_highlight_json); - // cx.add_async_action(Editor::format); - // cx.register_action_type(Editor::restart_language_server); - // cx.register_action_type(Editor::show_character_palette); - // cx.add_async_action(Editor::confirm_completion); - // cx.add_async_action(Editor::confirm_code_action); - // cx.add_async_action(Editor::rename); - // cx.add_async_action(Editor::confirm_rename); - // cx.add_async_action(Editor::find_all_references); - // cx.register_action_type(Editor::next_copilot_suggestion); - // cx.register_action_type(Editor::previous_copilot_suggestion); - // cx.register_action_type(Editor::copilot_suggest); - // cx.register_action_type(Editor::context_menu_first); - // cx.register_action_type(Editor::context_menu_prev); - // cx.register_action_type(Editor::context_menu_next); - // cx.register_action_type(Editor::context_menu_last); hover_popover::init(cx); scroll::actions::init(cx); diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index 289d858c62a8d246fba18561f606dcf36ec26eac..ba4ed077f22f7aa1e17e8ca66359fafe32fb57a3 100644 --- a/crates/gpui2/src/action.rs +++ b/crates/gpui2/src/action.rs @@ -1,8 +1,8 @@ use crate::SharedString; use anyhow::{anyhow, Context, Result}; use collections::{HashMap, HashSet}; -use ctor::ctor; -use parking_lot::Mutex; +use lazy_static::lazy_static; +use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; use serde::Deserialize; use std::any::{type_name, Any}; @@ -21,23 +21,41 @@ pub trait Action: std::fmt::Debug + 'static { type ActionBuilder = fn(json: Option) -> anyhow::Result>; -#[ctor] -static ACTION_BUILDERS: Mutex> = Mutex::default(); +lazy_static! { + static ref ACTION_REGISTRY: RwLock = RwLock::default(); +} + +#[derive(Default)] +struct ActionRegistry { + builders_by_name: 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() { - ACTION_BUILDERS.lock().insert(A::qualified_name(), A::build); + let name = A::qualified_name(); + let mut lock = ACTION_REGISTRY.write(); + lock.builders_by_name.insert(name.clone(), A::build); + lock.all_names.push(name); } /// 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 lock = ACTION_REGISTRY.read(); let build_action = lock + .builders_by_name .get(name) .ok_or_else(|| anyhow!("no action type registered for {}", name))?; (build_action)(params) } +pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> { + let lock = ACTION_REGISTRY.read(); + RwLockReadGuard::map(lock, |registry: &ActionRegistry| { + registry.all_names.as_slice() + }) +} + // actions defines structs that can be used as actions. #[macro_export] macro_rules! actions { diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 63d2143a6726773be7508c77c72bdd6b1052fe3d..0267f4723686bcf5c60e52da09e079f5ac7393b5 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -17,9 +17,9 @@ use crate::{ current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, - PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SharedString, - SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, - View, Window, WindowContext, WindowHandle, WindowId, + PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet, + Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, + WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -140,7 +140,6 @@ impl App { } } -type ActionBuilder = fn(json: Option) -> anyhow::Result>; pub(crate) type FrameCallback = Box; type Handler = Box bool + 'static>; type Listener = Box bool + 'static>; @@ -176,7 +175,6 @@ pub struct AppContext { pub(crate) keymap: Arc>, pub(crate) global_action_listeners: HashMap>>, - action_builders: HashMap, pending_effects: VecDeque, pub(crate) pending_notifications: HashSet, pub(crate) pending_global_notifications: HashSet, @@ -234,7 +232,6 @@ impl AppContext { windows: SlotMap::with_key(), keymap: Arc::new(Mutex::new(Keymap::default())), global_action_listeners: HashMap::default(), - action_builders: HashMap::default(), pending_effects: VecDeque::new(), pending_notifications: HashSet::default(), pending_global_notifications: HashSet::default(), @@ -695,10 +692,6 @@ impl AppContext { ) } - pub fn all_action_names<'a>(&'a self) -> impl Iterator + 'a { - self.action_builders.keys().cloned() - } - /// Move the global of the given type to the stack. pub(crate) fn lease_global(&mut self) -> GlobalLease { GlobalLease::new( @@ -761,24 +754,6 @@ impl AppContext { })); } - /// Register an action type to allow it to be referenced in keymaps. - pub fn register_action_type(&mut self) { - self.action_builders.insert(A::qualified_name(), A::build); - } - - /// Construct an action based on its name and parameters. - pub fn build_action( - &mut self, - name: &str, - params: Option, - ) -> Result> { - let build = self - .action_builders - .get(name) - .ok_or_else(|| anyhow!("no action type registered for {}", name))?; - (build)(params) - } - /// Event handlers propagate events by default. Call this method to stop dispatching to /// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is /// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by diff --git a/crates/gpui2_macros/src/register_action.rs b/crates/gpui2_macros/src/register_action.rs index 77516cdec8278edc28e011800795f17294e5c2ee..ab00daf4773a620200d2edd8129dd666a233bfde 100644 --- a/crates/gpui2_macros/src/register_action.rs +++ b/crates/gpui2_macros/src/register_action.rs @@ -1,14 +1,14 @@ // Input: // -// struct Foo {} +// struct FooBar {} // Output: // -// struct Foo {} +// struct FooBar {} // // #[allow(non_snake_case)] // #[gpui2::ctor] -// fn register_Foo_builder() { +// fn register_foobar_builder() { // gpui2::register_action_builder::() // } use proc_macro::TokenStream; diff --git a/crates/settings2/src/keymap_file.rs b/crates/settings2/src/keymap_file.rs index e51bd76e5e0c9e0d86241a9c3bb816c90d0e1063..ec1f02a95d103a78f05a5f8f028f4ce5cc6b709a 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." ))); }; - cx.build_action(&name, Some(data)) + gpui::build_action(&name, Some(data)) } - Value::String(name) => cx.build_action(&name, None), + Value::String(name) => gpui::build_action(&name, None), Value::Null => Ok(no_action()), _ => { return Some(Err(anyhow!("Expected two-element array, got {action:?}"))) diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index 0f580d0cbf171ef32b2c83465ad8601785304227..57f9869859710bfd07967af6e8170c8048634124 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -15,8 +15,6 @@ impl FocusStory { KeyBinding::new("cmd-a", ActionB, Some("child-1")), KeyBinding::new("cmd-c", ActionC, None), ]); - cx.register_action_type::(); - cx.register_action_type::(); cx.build_view(move |cx| Self {}) } diff --git a/crates/zed2/src/languages/json.rs b/crates/zed2/src/languages/json.rs index 63f909ae2a2e264ea672dee48e305ba1be82e066..cf9b33d9683cadd9f5b27c41104d215becd1110d 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 = cx.all_action_names().collect::>(); + let action_names = gpui::all_action_names(); let staff_mode = cx.is_staff(); let language_names = &self.languages.language_names(); let settings_schema = cx.global::().json_schema( From fdc9ea7f9b6d473f2d60768236475f66484d13ef Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 7 Nov 2023 21:26:51 -0700 Subject: [PATCH 3/5] Docs and cleanup --- crates/editor2/src/editor.rs | 123 +++++++++++++++++++++++++++++++++++ crates/gpui2/src/action.rs | 118 ++++++++++++++++++++++----------- 2 files changed, 204 insertions(+), 37 deletions(-) diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index e7936b8ebc226a569775df417362f03818964753..eaf91b089b0b5c14bcafda4ac98b730198635971 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -406,6 +406,129 @@ pub fn init_settings(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) { init_settings(cx); + // cx.register_action_type(Editor::new_file); + // cx.register_action_type(Editor::new_file_in_direction); + // cx.register_action_type(Editor::cancel); + // cx.register_action_type(Editor::newline); + // cx.register_action_type(Editor::newline_above); + // cx.register_action_type(Editor::newline_below); + // cx.register_action_type(Editor::backspace); + // cx.register_action_type(Editor::delete); + // cx.register_action_type(Editor::tab); + // cx.register_action_type(Editor::tab_prev); + // cx.register_action_type(Editor::indent); + // cx.register_action_type(Editor::outdent); + // cx.register_action_type(Editor::delete_line); + // cx.register_action_type(Editor::join_lines); + // cx.register_action_type(Editor::sort_lines_case_sensitive); + // cx.register_action_type(Editor::sort_lines_case_insensitive); + // cx.register_action_type(Editor::reverse_lines); + // cx.register_action_type(Editor::shuffle_lines); + // cx.register_action_type(Editor::convert_to_upper_case); + // cx.register_action_type(Editor::convert_to_lower_case); + // cx.register_action_type(Editor::convert_to_title_case); + // cx.register_action_type(Editor::convert_to_snake_case); + // cx.register_action_type(Editor::convert_to_kebab_case); + // cx.register_action_type(Editor::convert_to_upper_camel_case); + // cx.register_action_type(Editor::convert_to_lower_camel_case); + // cx.register_action_type(Editor::delete_to_previous_word_start); + // cx.register_action_type(Editor::delete_to_previous_subword_start); + // cx.register_action_type(Editor::delete_to_next_word_end); + // cx.register_action_type(Editor::delete_to_next_subword_end); + // cx.register_action_type(Editor::delete_to_beginning_of_line); + // cx.register_action_type(Editor::delete_to_end_of_line); + // cx.register_action_type(Editor::cut_to_end_of_line); + // cx.register_action_type(Editor::duplicate_line); + // cx.register_action_type(Editor::move_line_up); + // cx.register_action_type(Editor::move_line_down); + // cx.register_action_type(Editor::transpose); + // cx.register_action_type(Editor::cut); + // cx.register_action_type(Editor::copy); + // cx.register_action_type(Editor::paste); + // cx.register_action_type(Editor::undo); + // cx.register_action_type(Editor::redo); + // cx.register_action_type(Editor::move_page_up); + // cx.register_action_type::(); + // cx.register_action_type(Editor::move_page_down); + // cx.register_action_type(Editor::next_screen); + // cx.register_action_type::(); + // cx.register_action_type::(); + // cx.register_action_type(Editor::move_to_previous_word_start); + // cx.register_action_type(Editor::move_to_previous_subword_start); + // cx.register_action_type(Editor::move_to_next_word_end); + // cx.register_action_type(Editor::move_to_next_subword_end); + // cx.register_action_type(Editor::move_to_beginning_of_line); + // cx.register_action_type(Editor::move_to_end_of_line); + // cx.register_action_type(Editor::move_to_start_of_paragraph); + // cx.register_action_type(Editor::move_to_end_of_paragraph); + // cx.register_action_type(Editor::move_to_beginning); + // cx.register_action_type(Editor::move_to_end); + // cx.register_action_type(Editor::select_up); + // cx.register_action_type(Editor::select_down); + // cx.register_action_type(Editor::select_left); + // cx.register_action_type(Editor::select_right); + // cx.register_action_type(Editor::select_to_previous_word_start); + // cx.register_action_type(Editor::select_to_previous_subword_start); + // cx.register_action_type(Editor::select_to_next_word_end); + // cx.register_action_type(Editor::select_to_next_subword_end); + // cx.register_action_type(Editor::select_to_beginning_of_line); + // cx.register_action_type(Editor::select_to_end_of_line); + // cx.register_action_type(Editor::select_to_start_of_paragraph); + // cx.register_action_type(Editor::select_to_end_of_paragraph); + // cx.register_action_type(Editor::select_to_beginning); + // cx.register_action_type(Editor::select_to_end); + // cx.register_action_type(Editor::select_all); + // cx.register_action_type(Editor::select_all_matches); + // cx.register_action_type(Editor::select_line); + // cx.register_action_type(Editor::split_selection_into_lines); + // cx.register_action_type(Editor::add_selection_above); + // cx.register_action_type(Editor::add_selection_below); + // cx.register_action_type(Editor::select_next); + // cx.register_action_type(Editor::select_previous); + // cx.register_action_type(Editor::toggle_comments); + // cx.register_action_type(Editor::select_larger_syntax_node); + // cx.register_action_type(Editor::select_smaller_syntax_node); + // cx.register_action_type(Editor::move_to_enclosing_bracket); + // cx.register_action_type(Editor::undo_selection); + // cx.register_action_type(Editor::redo_selection); + // cx.register_action_type(Editor::go_to_diagnostic); + // cx.register_action_type(Editor::go_to_prev_diagnostic); + // cx.register_action_type(Editor::go_to_hunk); + // cx.register_action_type(Editor::go_to_prev_hunk); + // cx.register_action_type(Editor::go_to_definition); + // cx.register_action_type(Editor::go_to_definition_split); + // cx.register_action_type(Editor::go_to_type_definition); + // cx.register_action_type(Editor::go_to_type_definition_split); + // cx.register_action_type(Editor::fold); + // cx.register_action_type(Editor::fold_at); + // cx.register_action_type(Editor::unfold_lines); + // cx.register_action_type(Editor::unfold_at); + // cx.register_action_type(Editor::gutter_hover); + // cx.register_action_type(Editor::fold_selected_ranges); + // cx.register_action_type(Editor::show_completions); + // cx.register_action_type(Editor::toggle_code_actions); + // cx.register_action_type(Editor::open_excerpts); + // cx.register_action_type(Editor::toggle_soft_wrap); + // cx.register_action_type(Editor::toggle_inlay_hints); + // cx.register_action_type(Editor::reveal_in_finder); + // cx.register_action_type(Editor::copy_path); + // cx.register_action_type(Editor::copy_relative_path); + // cx.register_action_type(Editor::copy_highlight_json); + // cx.add_async_action(Editor::format); + // cx.register_action_type(Editor::restart_language_server); + // cx.register_action_type(Editor::show_character_palette); + // cx.add_async_action(Editor::confirm_completion); + // cx.add_async_action(Editor::confirm_code_action); + // cx.add_async_action(Editor::rename); + // cx.add_async_action(Editor::confirm_rename); + // cx.add_async_action(Editor::find_all_references); + // cx.register_action_type(Editor::next_copilot_suggestion); + // cx.register_action_type(Editor::previous_copilot_suggestion); + // cx.register_action_type(Editor::copilot_suggest); + // cx.register_action_type(Editor::context_menu_first); + // cx.register_action_type(Editor::context_menu_prev); + // cx.register_action_type(Editor::context_menu_next); + // cx.register_action_type(Editor::context_menu_last); hover_popover::init(cx); scroll::actions::init(cx); diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index ba4ed077f22f7aa1e17e8ca66359fafe32fb57a3..6e0bcedf4de1b76ac73ccd05c449aa9cfaca2517 100644 --- a/crates/gpui2/src/action.rs +++ b/crates/gpui2/src/action.rs @@ -6,6 +6,49 @@ use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; use serde::Deserialize; use std::any::{type_name, Any}; +/// Actions are used to implement keyboard-driven UI. +/// When you declare an action, you can bind keys to the action in the keymap and +/// listeners for that action in the element tree. +/// +/// To declare a list of simple actions, you can use the actions! macro, which defines a simple unit struct +/// action for each listed action name. +/// ```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 +/// ``` +/// #[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. +/// +/// ``` +/// #[gpui::register_action] +/// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::fmt::Debug)] +/// pub struct Paste { +/// pub content: SharedString, +/// } +/// +/// impl std::default::Default for Paste { +/// fn default() -> Self { +/// Self { +/// content: SharedString::from("🍝"), +/// } +/// } +/// } +/// ``` pub trait Action: std::fmt::Debug + 'static { fn qualified_name() -> SharedString where @@ -19,6 +62,44 @@ pub trait Action: std::fmt::Debug + 'static { fn as_any(&self) -> &dyn Any; } +// Types become actions by satisfying a list of trait bounds. +impl Action for A +where + A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static, +{ + fn qualified_name() -> SharedString { + // todo!() remove the 2 replacement when migration is done + type_name::().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 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 + } +} + type ActionBuilder = fn(json: Option) -> anyhow::Result>; lazy_static! { @@ -84,43 +165,6 @@ macro_rules! actions { }; } -impl Action for A -where - A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static, -{ - fn qualified_name() -> SharedString { - // todo!() remove the 2 replacement when migration is done - type_name::().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 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 - } -} - #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct DispatchContext { set: HashSet, From 2a55b0dbfbd1e691b71d25bb7a3fc69c3e1d80cc Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 7 Nov 2023 21:48:47 -0700 Subject: [PATCH 4/5] Simplify actions macro. --- crates/gpui2/src/action.rs | 20 ++++--------- crates/gpui2_macros/src/action.rs | 40 ++++++++++++++++++++----- crates/gpui2_macros/src/gpui2_macros.rs | 10 +++++-- 3 files changed, 45 insertions(+), 25 deletions(-) diff --git a/crates/gpui2/src/action.rs b/crates/gpui2/src/action.rs index 6e0bcedf4de1b76ac73ccd05c449aa9cfaca2517..4d89ba1826e03a75a71b2fbd27018b511d784a02 100644 --- a/crates/gpui2/src/action.rs +++ b/crates/gpui2/src/action.rs @@ -137,7 +137,8 @@ pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> { }) } -// actions defines structs that can be used as actions. +/// Defines unit structs that can be used as actions. +/// To use more complex data types as actions, annotate your type with the #[action] macro. #[macro_export] macro_rules! actions { () => {}; @@ -148,21 +149,10 @@ macro_rules! actions { 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)* } - }; - ( $name:ident, $($rest:tt)* ) => { actions!($name); actions!($($rest)*); }; - - ( $name:ident { $($token:tt)* }, $($rest:tt)* ) => { - actions!($name { $($token)* }); - actions!($($rest)*); - }; } #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -408,17 +398,17 @@ mod tests { #[test] fn test_actions_definition() { { - actions!(A, B { field: i32 }, C, D, E, F {}, G); + actions!(A, B, C, D, E, F, G); } { actions!( A, - B { field: i32 }, + B, C, D, E, - F {}, + F, G, // Don't wrap, test the trailing comma ); } diff --git a/crates/gpui2_macros/src/action.rs b/crates/gpui2_macros/src/action.rs index 9a8ec0d8b09d82d85a9faa1d6664a44482612822..e4f54e58d5c601561925be05e962626dd5fd5d64 100644 --- a/crates/gpui2_macros/src/action.rs +++ b/crates/gpui2_macros/src/action.rs @@ -1,13 +1,17 @@ // Input: // // #[action] -// struct Foo {} +// struct Foo { +// bar: String, +// } // Output: // // #[gpui::register_action] // #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)] -// struct Foo {} +// struct Foo { +// bar: String, +// } use proc_macro::TokenStream; use quote::quote; @@ -16,15 +20,35 @@ 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 attrs = input + .attrs + .into_iter() + .filter(|attr| !attr.path.is_ident("action")) + .collect::>(); - let data_tokens = quote! { #data }.into(); - - let expanded = quote! { + let attributes = quote! { #[gpui::register_action] #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::default::Default, std::fmt::Debug)] - struct #name { #data } + #(#attrs)* + }; + + let output = match input.data { + syn::Data::Struct(ref struct_data) => { + let fields = &struct_data.fields; + quote! { + #attributes + struct #name { #fields } + } + } + syn::Data::Enum(ref enum_data) => { + let variants = &enum_data.variants; + quote! { + #attributes + enum #name { #variants } + } + } + _ => panic!("Expected a struct or an enum."), }; - TokenStream::from(expanded) + TokenStream::from(output) } diff --git a/crates/gpui2_macros/src/gpui2_macros.rs b/crates/gpui2_macros/src/gpui2_macros.rs index d1271ad041cce5ffe4f8e758e394c9710cefd6b1..80b67e1a12393d76d1ecffc5a342dc8eaefacc40 100644 --- a/crates/gpui2_macros/src/gpui2_macros.rs +++ b/crates/gpui2_macros/src/gpui2_macros.rs @@ -1,15 +1,21 @@ -use proc_macro::TokenStream; - +mod action; mod derive_component; mod register_action; mod style_helpers; mod test; +use proc_macro::TokenStream; + #[proc_macro] 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_attribute] pub fn register_action(attr: TokenStream, item: TokenStream) -> TokenStream { register_action::register_action(attr, item) From 408edaae399123bd51e4399d3192b33d5a905aa7 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 7 Nov 2023 21:58:46 -0700 Subject: [PATCH 5/5] Remove call to removed method --- crates/go_to_line2/src/go_to_line.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index 13d283ecff245abb11bafd75cabca94ac79e80dc..b8b91b16468c0e97e25946c3e24be5a9e062dd21 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -4,7 +4,6 @@ use workspace::ModalRegistry; actions!(Toggle); pub fn init(cx: &mut AppContext) { - cx.register_action_type::(); cx.global_mut::() .register_modal(Toggle, |_, cx| { // if let Some(editor) = workspace