diff --git a/.rules b/.rules index b9eea27b67..da009f1877 100644 --- a/.rules +++ b/.rules @@ -100,9 +100,7 @@ Often event handlers will want to update the entity that's in the current `Conte Actions are dispatched via user keyboard interaction or in code via `window.dispatch_action(SomeAction.boxed_clone(), cx)` or `focus_handle.dispatch_action(&SomeAction, window, cx)`. -Actions which have no data inside are created and registered with the `actions!(some_namespace, [SomeAction, AnotherAction])` macro call. - -Actions that do have data must implement `Clone, Default, PartialEq, Deserialize, JsonSchema` and can be registered with an `impl_actions!(some_namespace, [SomeActionWithData])` macro call. +Actions with no data defined with the `actions!(some_namespace, [SomeAction, AnotherAction])` macro call. Otherwise the `Action` derive macro is used. Doc comments on actions are displayed to the user. Action handlers can be registered on an element via the event handler `.on_action(|action, window, cx| ...)`. Like other event handlers, this is often used with `cx.listener`. diff --git a/Cargo.lock b/Cargo.lock index a4c34e87c1..518b790a2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19970,6 +19970,7 @@ dependencies = [ "inline_completion_button", "inspector_ui", "install_cli", + "itertools 0.14.0", "jj_ui", "journal", "language", @@ -19994,6 +19995,7 @@ dependencies = [ "parking_lot", "paths", "picker", + "pretty_assertions", "profiling", "project", "project_panel", diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index eee0ab1993..0a9649e318 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -27,7 +27,7 @@ use assistant_slash_command::SlashCommandRegistry; use client::Client; use feature_flags::FeatureFlagAppExt as _; use fs::Fs; -use gpui::{App, Entity, actions, impl_actions}; +use gpui::{Action, App, Entity, actions}; use language::LanguageRegistry; use language_model::{ ConfiguredModel, LanguageModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, @@ -84,13 +84,15 @@ actions!( ] ); -#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema)] +#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)] +#[action(namespace = agent)] pub struct NewThread { #[serde(default)] from_thread_id: Option, } -#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = agent)] pub struct ManageProfiles { #[serde(default)] pub customize_tools: Option, @@ -104,8 +106,6 @@ impl ManageProfiles { } } -impl_actions!(agent, [NewThread, ManageProfiles]); - #[derive(Clone)] pub(crate) enum ModelUsageContext { Thread(Entity), diff --git a/crates/assistant_context_editor/src/context_editor.rs b/crates/assistant_context_editor/src/context_editor.rs index 105778117e..57499bae6c 100644 --- a/crates/assistant_context_editor/src/context_editor.rs +++ b/crates/assistant_context_editor/src/context_editor.rs @@ -27,11 +27,11 @@ use editor::{FoldPlaceholder, display_map::CreaseId}; use fs::Fs; use futures::FutureExt; use gpui::{ - Animation, AnimationExt, AnyElement, AnyView, App, ClipboardEntry, ClipboardItem, Empty, - Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement, + Action, Animation, AnimationExt, AnyElement, AnyView, App, ClipboardEntry, ClipboardItem, + Empty, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement, IntoElement, ParentElement, Pixels, Render, RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation, WeakEntity, actions, - div, img, impl_internal_actions, percentage, point, prelude::*, pulsating_between, size, + div, img, percentage, point, prelude::*, pulsating_between, size, }; use indexed_docs::IndexedDocsStore; use language::{ @@ -99,14 +99,13 @@ actions!( ] ); -#[derive(PartialEq, Clone)] +#[derive(PartialEq, Clone, Action)] +#[action(namespace = assistant, no_json, no_register)] pub enum InsertDraggedFiles { ProjectPaths(Vec), ExternalFiles(Vec), } -impl_internal_actions!(assistant, [InsertDraggedFiles]); - #[derive(Copy, Clone, Debug, PartialEq)] struct ScrollPosition { offset_before_cursor: gpui::Point, diff --git a/crates/assistant_context_editor/src/language_model_selector.rs b/crates/assistant_context_editor/src/language_model_selector.rs index b1cf3a050c..d9d11231ed 100644 --- a/crates/assistant_context_editor/src/language_model_selector.rs +++ b/crates/assistant_context_editor/src/language_model_selector.rs @@ -4,8 +4,7 @@ use collections::{HashSet, IndexMap}; use feature_flags::ZedProFeatureFlag; use fuzzy::{StringMatch, StringMatchCandidate, match_strings}; use gpui::{ - Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task, - action_with_deprecated_aliases, + Action, AnyElement, App, BackgroundExecutor, DismissEvent, Subscription, Task, actions, }; use language_model::{ AuthenticateError, ConfiguredModel, LanguageModel, LanguageModelProviderId, @@ -16,12 +15,11 @@ use picker::{Picker, PickerDelegate}; use proto::Plan; use ui::{ListItem, ListItemSpacing, prelude::*}; -action_with_deprecated_aliases!( +actions!( agent, - ToggleModelSelector, [ - "assistant::ToggleModelSelector", - "assistant2::ToggleModelSelector" + #[action(deprecated_aliases = ["assistant::ToggleModelSelector", "assistant2::ToggleModelSelector"])] + ToggleModelSelector ] ); diff --git a/crates/docs_preprocessor/src/main.rs b/crates/docs_preprocessor/src/main.rs index 8ec27a02a7..c8e945c7e8 100644 --- a/crates/docs_preprocessor/src/main.rs +++ b/crates/docs_preprocessor/src/main.rs @@ -247,7 +247,7 @@ fn dump_all_gpui_actions() -> Vec { .map(|action| ActionDef { name: action.name, human_name: command_palette::humanize_action_name(action.name), - deprecated_aliases: action.aliases, + deprecated_aliases: action.deprecated_aliases, }) .collect::>(); diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 5a66c37bee..b8a3e5efa7 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -1,24 +1,27 @@ //! This module contains all actions supported by [`Editor`]. use super::*; -use gpui::{action_as, action_with_deprecated_aliases, actions}; +use gpui::{Action, actions}; use schemars::JsonSchema; use util::serde::default_true; -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct SelectNext { #[serde(default)] pub replace_newest: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct SelectPrevious { #[serde(default)] pub replace_newest: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct MoveToBeginningOfLine { #[serde(default = "default_true")] @@ -27,7 +30,8 @@ pub struct MoveToBeginningOfLine { pub stop_at_indent: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct SelectToBeginningOfLine { #[serde(default)] @@ -36,42 +40,48 @@ pub struct SelectToBeginningOfLine { pub stop_at_indent: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct DeleteToBeginningOfLine { #[serde(default)] pub(super) stop_at_indent: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct MovePageUp { #[serde(default)] pub(super) center_cursor: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct MovePageDown { #[serde(default)] pub(super) center_cursor: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct MoveToEndOfLine { #[serde(default = "default_true")] pub stop_at_soft_wraps: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct SelectToEndOfLine { #[serde(default)] pub(super) stop_at_soft_wraps: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct ToggleCodeActions { // Source from which the action was deployed. @@ -91,28 +101,32 @@ pub enum CodeActionSource { QuickActionBar, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct ConfirmCompletion { #[serde(default)] pub item_ix: Option, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct ComposeCompletion { #[serde(default)] pub item_ix: Option, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct ConfirmCodeAction { #[serde(default)] pub item_ix: Option, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct ToggleComments { #[serde(default)] @@ -121,83 +135,96 @@ pub struct ToggleComments { pub ignore_indent: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct MoveUpByLines { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct MoveDownByLines { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct SelectUpByLines { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct SelectDownByLines { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct ExpandExcerpts { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct ExpandExcerptsUp { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct ExpandExcerptsDown { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct ShowCompletions { #[serde(default)] pub(super) trigger: Option, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] pub struct HandleInput(pub String); -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct DeleteToNextWordEnd { #[serde(default)] pub ignore_newlines: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct DeleteToPreviousWordStart { #[serde(default)] pub ignore_newlines: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] pub struct FoldAtLevel(pub u32); -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = editor)] #[serde(deny_unknown_fields)] pub struct SpawnNearestTask { #[serde(default)] @@ -211,41 +238,16 @@ pub enum UuidVersion { V7, } -impl_actions!( - editor, +actions!(debugger, [RunToCursor, EvaluateSelectedText]); + +actions!( + go_to_line, [ - ComposeCompletion, - ConfirmCodeAction, - ConfirmCompletion, - DeleteToBeginningOfLine, - DeleteToNextWordEnd, - DeleteToPreviousWordStart, - ExpandExcerpts, - ExpandExcerptsDown, - ExpandExcerptsUp, - HandleInput, - MoveDownByLines, - MovePageDown, - MovePageUp, - MoveToBeginningOfLine, - MoveToEndOfLine, - MoveUpByLines, - SelectDownByLines, - SelectNext, - SelectPrevious, - SelectToBeginningOfLine, - SelectToEndOfLine, - SelectUpByLines, - SpawnNearestTask, - ShowCompletions, - ToggleCodeActions, - ToggleComments, - FoldAtLevel, + #[action(name = "Toggle")] + ToggleGoToLine ] ); -actions!(debugger, [RunToCursor, EvaluateSelectedText]); - actions!( editor, [ @@ -296,6 +298,8 @@ actions!( DuplicateLineDown, DuplicateLineUp, DuplicateSelection, + #[action(deprecated_aliases = ["editor::ExpandAllHunkDiffs"])] + ExpandAllDiffHunks, ExpandMacroRecursively, FindAllReferences, FindNextMatch, @@ -365,6 +369,8 @@ actions!( OpenProposedChangesEditor, OpenDocs, OpenPermalinkToLine, + #[action(deprecated_aliases = ["editor::OpenFile"])] + OpenSelectedFilename, OpenSelectionsInMultibuffer, OpenUrl, OrganizeImports, @@ -443,6 +449,8 @@ actions!( SwapSelectionEnds, SetMark, ToggleRelativeLineNumbers, + #[action(deprecated_aliases = ["editor::ToggleHunkDiff"])] + ToggleSelectedDiffHunks, ToggleSelectionMenu, ToggleSoftWrap, ToggleTabBar, @@ -456,9 +464,3 @@ actions!( UniqueLinesCaseSensitive, ] ); - -action_as!(go_to_line, ToggleGoToLine as Toggle); - -action_with_deprecated_aliases!(editor, OpenSelectedFilename, ["editor::OpenFile"]); -action_with_deprecated_aliases!(editor, ToggleSelectedDiffHunks, ["editor::ToggleHunkDiff"]); -action_with_deprecated_aliases!(editor, ExpandAllDiffHunks, ["editor::ExpandAllHunkDiffs"]); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4f391a7b4f..568e9062c8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -96,7 +96,7 @@ use gpui::{ MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, - div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, + div, point, prelude::*, pulsating_between, px, relative, size, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file}; diff --git a/crates/git/src/git.rs b/crates/git/src/git.rs index e2c6e54993..bb8f39f127 100644 --- a/crates/git/src/git.rs +++ b/crates/git/src/git.rs @@ -9,9 +9,7 @@ pub use crate::hosting_provider::*; pub use crate::remote::*; use anyhow::{Context as _, Result}; pub use git2 as libgit; -use gpui::action_with_deprecated_aliases; -use gpui::actions; -use gpui::impl_action_with_deprecated_aliases; +use gpui::{Action, actions}; pub use repository::WORK_DIRECTORY_REPO_PATH; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -36,7 +34,11 @@ actions!( ToggleStaged, StageAndNext, UnstageAndNext, + #[action(deprecated_aliases = ["editor::RevertSelectedHunks"])] + Restore, // per-file + #[action(deprecated_aliases = ["editor::ToggleGitBlame"])] + Blame, StageFile, UnstageFile, // repo-wide @@ -61,16 +63,13 @@ actions!( ] ); -#[derive(Clone, Debug, Default, PartialEq, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)] +#[action(namespace = git, deprecated_aliases = ["editor::RevertFile"])] pub struct RestoreFile { #[serde(default)] pub skip_prompt: bool, } -impl_action_with_deprecated_aliases!(git, RestoreFile, ["editor::RevertFile"]); -action_with_deprecated_aliases!(git, Restore, ["editor::RevertSelectedHunks"]); -action_with_deprecated_aliases!(git, Blame, ["editor::ToggleGitBlame"]); - /// The length of a Git short SHA. pub const SHORT_SHA_LENGTH: usize = 7; diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index db617758b3..bfb37efd9a 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -1,6 +1,6 @@ -use crate::SharedString; use anyhow::{Context as _, Result}; use collections::HashMap; +pub use gpui_macros::Action; pub use no_action::{NoAction, is_no_action}; use serde_json::json; use std::{ @@ -8,28 +8,87 @@ use std::{ fmt::Display, }; -/// 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. +/// Defines and registers unit structs that can be used as actions. For more complex data types, derive `Action`. /// -/// 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 in the given namespace. -/// ```rust +/// For example: +/// +/// ``` /// actions!(editor, [MoveUp, MoveDown, MoveLeft, MoveRight, Newline]); /// ``` -/// More complex data types can also be actions, providing they implement Clone, PartialEq, -/// and serde_derive::Deserialize. -/// Use `impl_actions!` to automatically implement the action in the given namespace. +/// +/// This will create actions with names like `editor::MoveUp`, `editor::MoveDown`, etc. +/// +/// The namespace argument `editor` can also be omitted, though it is required for Zed actions. +#[macro_export] +macro_rules! actions { + ($namespace:path, [ $( $(#[$attr:meta])* $name:ident),* $(,)? ]) => { + $( + #[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::default::Default, ::std::fmt::Debug, gpui::Action)] + #[action(namespace = $namespace)] + $(#[$attr])* + pub struct $name; + )* + }; + ([ $( $(#[$attr:meta])* $name:ident),* $(,)? ]) => { + $( + #[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::default::Default, ::std::fmt::Debug, gpui::Action)] + $(#[$attr])* + pub struct $name; + )* + }; +} + +/// 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 in the given namespace. +/// /// ``` -/// #[derive(Clone, PartialEq, serde_derive::Deserialize)] +/// actions!(editor, [MoveUp, MoveDown, MoveLeft, MoveRight, Newline]); +/// ``` +/// +/// # Derive Macro +/// +/// More complex data types can also be actions, by using the derive macro for `Action`: +/// +/// ``` +/// #[derive(Clone, PartialEq, serde::Deserialize, schemars::JsonSchema, Action)] +/// #[action(namespace = editor)] /// pub struct SelectNext { /// pub replace_newest: bool, /// } -/// impl_actions!(editor, [SelectNext]); /// ``` /// -/// 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`. +/// The derive macro for `Action` requires that the type implement `Clone` and `PartialEq`. It also +/// requires `serde::Deserialize` and `schemars::JsonSchema` unless `#[action(no_json)]` is +/// specified. In Zed these trait impls are used to load keymaps from JSON. +/// +/// Multiple arguments separated by commas may be specified in `#[action(...)]`: +/// +/// - `namespace = some_namespace` sets the namespace. In Zed this is required. +/// +/// - `name = "ActionName"` overrides the action's name. This must not contain `::`. +/// +/// - `no_json` causes the `build` method to always error and `action_json_schema` to return `None`, +/// and allows actions not implement `serde::Serialize` and `schemars::JsonSchema`. +/// +/// - `no_register` skips registering the action. This is useful for implementing the `Action` trait +/// while not supporting invocation by name or JSON deserialization. +/// +/// - `deprecated_aliases = ["editor::SomeAction"]` specifies deprecated old names for the action. +/// These action names should *not* correspond to any actions that are registered. These old names +/// can then still be used to refer to invoke this action. In Zed, the keymap JSON schema will +/// accept these old names and provide warnings. +/// +/// - `deprecated = "Message about why this action is deprecation"` specifies a deprecation message. +/// In Zed, the keymap JSON schema will cause this to be displayed as a warning. +/// +/// # Manual Implementation +/// +/// 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`. /// /// ``` /// #[derive(gpui::private::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone)] @@ -50,10 +109,10 @@ pub trait Action: Any + Send { fn partial_eq(&self, action: &dyn Action) -> bool; /// Get the name of this action, for displaying in UI - fn name(&self) -> &str; + fn name(&self) -> &'static str; - /// Get the name of this action for debugging - fn debug_name() -> &'static str + /// Get the name of this action type (static) + fn name_for_type() -> &'static str where Self: Sized; @@ -73,13 +132,24 @@ pub trait Action: Any + Send { None } - /// A list of alternate, deprecated names for this action. + /// A list of alternate, deprecated names for this action. These names can still be used to + /// invoke the action. In Zed, the keymap JSON schema will accept these old names and provide + /// warnings. fn deprecated_aliases() -> &'static [&'static str] where Self: Sized, { &[] } + + /// Returns the deprecation message for this action, if any. In Zed, the keymap JSON schema will + /// cause this to be displayed as a warning. + fn deprecation_message() -> Option<&'static str> + where + Self: Sized, + { + None + } } impl std::fmt::Debug for dyn Action { @@ -141,10 +211,11 @@ impl Display for ActionBuildError { type ActionBuilder = fn(json: serde_json::Value) -> anyhow::Result>; pub(crate) struct ActionRegistry { - by_name: HashMap, - names_by_type_id: HashMap, - all_names: Vec, // So we can return a static slice. - deprecations: HashMap, + by_name: HashMap<&'static str, ActionData>, + names_by_type_id: HashMap, + all_names: Vec<&'static str>, // So we can return a static slice. + deprecated_aliases: HashMap<&'static str, &'static str>, // deprecated name -> preferred name + deprecation_messages: HashMap<&'static str, &'static str>, // action name -> deprecation message } impl Default for ActionRegistry { @@ -153,7 +224,8 @@ impl Default for ActionRegistry { by_name: Default::default(), names_by_type_id: Default::default(), all_names: Default::default(), - deprecations: Default::default(), + deprecated_aliases: Default::default(), + deprecation_messages: Default::default(), }; this.load_actions(); @@ -177,10 +249,11 @@ pub struct MacroActionBuilder(pub fn() -> MacroActionData); #[doc(hidden)] pub struct MacroActionData { pub name: &'static str, - pub aliases: &'static [&'static str], pub type_id: TypeId, pub build: ActionBuilder, pub json_schema: fn(&mut schemars::r#gen::SchemaGenerator) -> Option, + pub deprecated_aliases: &'static [&'static str], + pub deprecation_message: Option<&'static str>, } inventory::collect!(MacroActionBuilder); @@ -197,37 +270,40 @@ impl ActionRegistry { #[cfg(test)] pub(crate) fn load_action(&mut self) { self.insert_action(MacroActionData { - name: A::debug_name(), - aliases: A::deprecated_aliases(), + name: A::name_for_type(), type_id: TypeId::of::(), build: A::build, json_schema: A::action_json_schema, + deprecated_aliases: A::deprecated_aliases(), + deprecation_message: A::deprecation_message(), }); } fn insert_action(&mut self, action: MacroActionData) { - let name: SharedString = action.name.into(); self.by_name.insert( - name.clone(), + action.name, ActionData { build: action.build, json_schema: action.json_schema, }, ); - for &alias in action.aliases { - let alias: SharedString = alias.into(); + for &alias in action.deprecated_aliases { self.by_name.insert( - alias.clone(), + alias, ActionData { build: action.build, json_schema: action.json_schema, }, ); - self.deprecations.insert(alias.clone(), name.clone()); + self.deprecated_aliases.insert(alias, action.name); self.all_names.push(alias); } - self.names_by_type_id.insert(action.type_id, name.clone()); - self.all_names.push(name); + self.names_by_type_id.insert(action.type_id, action.name); + self.all_names.push(action.name); + if let Some(deprecation_msg) = action.deprecation_message { + self.deprecation_messages + .insert(action.name, deprecation_msg); + } } /// Construct an action based on its name and optional JSON parameters sourced from the keymap. @@ -235,10 +311,9 @@ impl ActionRegistry { let name = self .names_by_type_id .get(type_id) - .with_context(|| format!("no action type registered for {type_id:?}"))? - .clone(); + .with_context(|| format!("no action type registered for {type_id:?}"))?; - Ok(self.build_action(&name, None)?) + Ok(self.build_action(name, None)?) } /// Construct an action based on its name and optional JSON parameters sourced from the keymap. @@ -262,14 +337,14 @@ impl ActionRegistry { }) } - pub fn all_action_names(&self) -> &[SharedString] { + pub fn all_action_names(&self) -> &[&'static str] { self.all_names.as_slice() } pub fn action_schemas( &self, generator: &mut schemars::r#gen::SchemaGenerator, - ) -> Vec<(SharedString, Option)> { + ) -> Vec<(&'static str, Option)> { // Use the order from all_names so that the resulting schema has sensible order. self.all_names .iter() @@ -278,13 +353,17 @@ impl ActionRegistry { .by_name .get(name) .expect("All actions in all_names should be registered"); - (name.clone(), (action_data.json_schema)(generator)) + (*name, (action_data.json_schema)(generator)) }) .collect::>() } - pub fn action_deprecations(&self) -> &HashMap { - &self.deprecations + pub fn deprecated_aliases(&self) -> &HashMap<&'static str, &'static str> { + &self.deprecated_aliases + } + + pub fn deprecation_messages(&self) -> &HashMap<&'static str, &'static str> { + &self.deprecation_messages } } @@ -300,285 +379,18 @@ pub fn generate_list_of_all_registered_actions() -> Vec { actions } -/// Defines and registers unit structs that can be used as actions. -/// -/// To use more complex data types as actions, use `impl_actions!` -#[macro_export] -macro_rules! actions { - ($namespace:path, [ $($name:ident),* $(,)? ]) => { - $( - // Unfortunately rust-analyzer doesn't display the name due to - // https://github.com/rust-lang/rust-analyzer/issues/8092 - #[doc = stringify!($name)] - #[doc = "action generated by `gpui::actions!`"] - #[derive(::std::clone::Clone,::std::cmp::PartialEq, ::std::default::Default)] - pub struct $name; - - gpui::__impl_action!($namespace, $name, $name, - fn build(_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box> { - Ok(Box::new(Self)) - }, - fn action_json_schema( - _: &mut gpui::private::schemars::r#gen::SchemaGenerator, - ) -> Option { - None - } - ); - - gpui::register_action!($name); - )* - }; -} - -/// Defines and registers a unit struct that can be used as an actions, with a name that differs -/// from it's type name. -/// -/// To use more complex data types as actions, and rename them use `impl_action_as!` -#[macro_export] -macro_rules! action_as { - ($namespace:path, $name:ident as $visual_name:ident) => { - // Unfortunately rust-analyzer doesn't display the name due to - // https://github.com/rust-lang/rust-analyzer/issues/8092 - #[doc = stringify!($name)] - #[doc = "action generated by `gpui::action_as!`"] - #[derive( - ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, - )] - pub struct $name; - gpui::__impl_action!( - $namespace, - $name, - $visual_name, - fn build( - _: gpui::private::serde_json::Value, - ) -> gpui::Result<::std::boxed::Box> { - Ok(Box::new(Self)) - }, - fn action_json_schema( - generator: &mut gpui::private::schemars::r#gen::SchemaGenerator, - ) -> Option { - None - } - ); - - gpui::register_action!($name); - }; -} - -/// Defines and registers a unit struct that can be used as an action, with some deprecated aliases. -#[macro_export] -macro_rules! action_with_deprecated_aliases { - ($namespace:path, $name:ident, [$($alias:literal),* $(,)?]) => { - // Unfortunately rust-analyzer doesn't display the name due to - // https://github.com/rust-lang/rust-analyzer/issues/8092 - #[doc = stringify!($name)] - #[doc = "action, generated by `gpui::action_with_deprecated_aliases!`"] - #[derive( - ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, - )] - pub struct $name; - - gpui::__impl_action!( - $namespace, - $name, - $name, - fn build( - value: gpui::private::serde_json::Value, - ) -> gpui::Result<::std::boxed::Box> { - Ok(Box::new(Self)) - }, - - fn action_json_schema( - generator: &mut gpui::private::schemars::r#gen::SchemaGenerator, - ) -> Option { - None - }, - - fn deprecated_aliases() -> &'static [&'static str] { - &[ - $($alias),* - ] - } - ); - - gpui::register_action!($name); - }; -} - -/// Registers the action and implements the Action trait for any struct that implements Clone, -/// Default, PartialEq, serde_deserialize::Deserialize, and schemars::JsonSchema. -/// -/// Similar to `impl_actions!`, but only handles one struct, and registers some deprecated aliases. -#[macro_export] -macro_rules! impl_action_with_deprecated_aliases { - ($namespace:path, $name:ident, [$($alias:literal),* $(,)?]) => { - gpui::__impl_action!( - $namespace, - $name, - $name, - fn build( - value: gpui::private::serde_json::Value, - ) -> gpui::Result<::std::boxed::Box> { - Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::(value)?)) - }, - - fn action_json_schema( - generator: &mut gpui::private::schemars::r#gen::SchemaGenerator, - ) -> Option { - Some(::json_schema( - generator, - )) - }, - - fn deprecated_aliases() -> &'static [&'static str] { - &[ - $($alias),* - ] - } - ); - - gpui::register_action!($name); - }; -} - -/// Registers the action and implements the Action trait for any struct that implements Clone, -/// Default, PartialEq, serde_deserialize::Deserialize, and schemars::JsonSchema. -/// -/// Similar to `actions!`, but accepts structs with fields. -/// -/// Fields and variants that don't make sense for user configuration should be annotated with -/// #[serde(skip)]. -#[macro_export] -macro_rules! impl_actions { - ($namespace:path, [ $($name:ident),* $(,)? ]) => { - $( - gpui::__impl_action!($namespace, $name, $name, - fn build(value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box> { - Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::(value)?)) - }, - fn action_json_schema( - generator: &mut gpui::private::schemars::r#gen::SchemaGenerator, - ) -> Option { - Some(::json_schema( - generator, - )) - } - ); - - gpui::register_action!($name); - )* - }; -} - -/// Implements the Action trait for internal action structs that implement Clone, Default, -/// PartialEq. The purpose of this is to conveniently define values that can be passed in `dyn -/// Action`. -/// -/// These actions are internal and so are not registered and do not support deserialization. -#[macro_export] -macro_rules! impl_internal_actions { - ($namespace:path, [ $($name:ident),* $(,)? ]) => { - $( - gpui::__impl_action!($namespace, $name, $name, - fn build(value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box> { - gpui::Result::Err(gpui::private::anyhow::anyhow!( - concat!( - stringify!($namespace), - "::", - stringify!($visual_name), - " is an internal action, so cannot be built from JSON." - ))) - }, - fn action_json_schema( - generator: &mut gpui::private::schemars::r#gen::SchemaGenerator, - ) -> Option { - None - } - ); - )* - }; -} - -/// Implements the Action trait for a struct that implements Clone, Default, PartialEq, and -/// serde_deserialize::Deserialize. Allows you to rename the action visually, without changing the -/// struct's name. -/// -/// Fields and variants that don't make sense for user configuration should be annotated with -/// #[serde(skip)]. -#[macro_export] -macro_rules! impl_action_as { - ($namespace:path, $name:ident as $visual_name:tt ) => { - gpui::__impl_action!( - $namespace, - $name, - $visual_name, - fn build( - value: gpui::private::serde_json::Value, - ) -> gpui::Result<::std::boxed::Box> { - Ok(std::boxed::Box::new( - gpui::private::serde_json::from_value::(value)?, - )) - }, - fn action_json_schema( - generator: &mut gpui::private::schemars::r#gen::SchemaGenerator, - ) -> Option { - Some(::json_schema( - generator, - )) - } - ); - - gpui::register_action!($name); - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __impl_action { - ($namespace:path, $name:ident, $visual_name:tt, $($items:item),*) => { - impl gpui::Action for $name { - fn name(&self) -> &'static str - { - concat!( - stringify!($namespace), - "::", - stringify!($visual_name), - ) - } - - fn debug_name() -> &'static str - where - Self: ::std::marker::Sized - { - concat!( - stringify!($namespace), - "::", - stringify!($visual_name), - ) - } - - 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()) - } - - - $($items)* - } - }; -} - mod no_action { use crate as gpui; use std::any::Any as _; - actions!(zed, [NoAction]); + actions!( + zed, + [ + /// Action with special handling which unbinds the keybinding this is associated with, + /// if it is the highest precedence match. + NoAction + ] + ); /// Returns whether or not this action represents a removed key binding. pub fn is_no_action(action: &dyn gpui::Action) -> bool { diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 6c8b48873d..109d5e7454 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -39,8 +39,8 @@ use crate::{ Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource, - SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, - WindowAppearance, WindowHandle, WindowId, WindowInvalidator, + SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance, + WindowHandle, WindowId, WindowInvalidator, colors::{Colors, GlobalColors}, current_platform, hash, init_app_menus, }; @@ -1374,7 +1374,7 @@ impl App { /// Get all action names that have been registered. Note that registration only allows for /// actions to be built dynamically, and is unrelated to binding actions in the element tree. - pub fn all_action_names(&self) -> &[SharedString] { + pub fn all_action_names(&self) -> &[&'static str] { self.actions.all_action_names() } @@ -1389,13 +1389,18 @@ impl App { pub fn action_schemas( &self, generator: &mut schemars::r#gen::SchemaGenerator, - ) -> Vec<(SharedString, Option)> { + ) -> Vec<(&'static str, Option)> { self.actions.action_schemas(generator) } - /// Get a list of all deprecated action aliases and their canonical names. - pub fn action_deprecations(&self) -> &HashMap { - self.actions.action_deprecations() + /// Get a map from a deprecated action name to the canonical name. + pub fn deprecated_actions_to_preferred_actions(&self) -> &HashMap<&'static str, &'static str> { + self.actions.deprecated_aliases() + } + + /// Get a list of all action deprecation messages. + pub fn action_deprecation_messages(&self) -> &HashMap<&'static str, &'static str> { + self.actions.deprecation_messages() } /// Register a callback to be invoked when the application is about to quit. diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index 16cd8381cd..edd807da11 100644 --- a/crates/gpui/src/interactive.rs +++ b/crates/gpui/src/interactive.rs @@ -493,7 +493,7 @@ mod test { focus_handle: FocusHandle, } - actions!(test, [TestAction]); + actions!(test_only, [TestAction]); impl Render for TestView { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 8e2af9422b..a290a132c3 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -634,7 +634,7 @@ mod tests { "test::TestAction" } - fn debug_name() -> &'static str + fn name_for_type() -> &'static str where Self: ::std::marker::Sized, { diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs index ef088259de..b5dbab15c7 100644 --- a/crates/gpui/src/keymap.rs +++ b/crates/gpui/src/keymap.rs @@ -261,10 +261,10 @@ impl Keymap { mod tests { use super::*; use crate as gpui; - use gpui::{NoAction, actions}; + use gpui::NoAction; actions!( - keymap_test, + test_only, [ActionAlpha, ActionBeta, ActionGamma, ActionDelta,] ); diff --git a/crates/gpui/src/keymap/context.rs b/crates/gpui/src/keymap/context.rs index ae6589e23a..1221aa1224 100644 --- a/crates/gpui/src/keymap/context.rs +++ b/crates/gpui/src/keymap/context.rs @@ -425,12 +425,12 @@ mod tests { #[test] fn test_actions_definition() { { - actions!(test, [A, B, C, D, E, F, G]); + actions!(test_only, [A, B, C, D, E, F, G]); } { actions!( - test, + test_only, [ A, B, C, D, E, F, G, // Don't wrap, test the trailing comma ] diff --git a/crates/gpui/tests/action_macros.rs b/crates/gpui/tests/action_macros.rs index 5a76dfa9f3..f601639fc8 100644 --- a/crates/gpui/tests/action_macros.rs +++ b/crates/gpui/tests/action_macros.rs @@ -1,16 +1,22 @@ -use gpui::{actions, impl_actions}; +use gpui::{Action, actions}; use gpui_macros::register_action; use schemars::JsonSchema; use serde_derive::Deserialize; #[test] fn test_action_macros() { - actions!(test, [TestAction]); + actions!( + test_only, + [ + SomeAction, + /// Documented action + SomeActionWithDocs, + ] + ); - #[derive(PartialEq, Clone, Deserialize, JsonSchema)] - struct AnotherTestAction; - - impl_actions!(test, [AnotherTestAction]); + #[derive(PartialEq, Clone, Deserialize, JsonSchema, Action)] + #[action(namespace = test_only)] + struct AnotherSomeAction; #[derive(PartialEq, Clone, gpui::private::serde_derive::Deserialize)] struct RegisterableAction {} @@ -26,11 +32,11 @@ fn test_action_macros() { unimplemented!() } - fn name(&self) -> &str { + fn name(&self) -> &'static str { unimplemented!() } - fn debug_name() -> &'static str + fn name_for_type() -> &'static str where Self: Sized, { diff --git a/crates/gpui_macros/src/derive_action.rs b/crates/gpui_macros/src/derive_action.rs new file mode 100644 index 0000000000..c382ddd9c6 --- /dev/null +++ b/crates/gpui_macros/src/derive_action.rs @@ -0,0 +1,176 @@ +use crate::register_action::generate_register_action; +use proc_macro::TokenStream; +use proc_macro2::Ident; +use quote::quote; +use syn::{Data, DeriveInput, LitStr, Token, parse::ParseStream}; + +pub(crate) fn derive_action(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as DeriveInput); + + let struct_name = &input.ident; + let mut name_argument = None; + let mut deprecated_aliases = Vec::new(); + let mut no_json = false; + let mut no_register = false; + let mut namespace = None; + let mut deprecated = None; + + for attr in &input.attrs { + if attr.path().is_ident("action") { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("name") { + if name_argument.is_some() { + return Err(meta.error("'name' argument specified multiple times")); + } + meta.input.parse::()?; + let lit: LitStr = meta.input.parse()?; + name_argument = Some(lit.value()); + } else if meta.path.is_ident("namespace") { + if namespace.is_some() { + return Err(meta.error("'namespace' argument specified multiple times")); + } + meta.input.parse::()?; + let ident: Ident = meta.input.parse()?; + namespace = Some(ident.to_string()); + } else if meta.path.is_ident("no_json") { + if no_json { + return Err(meta.error("'no_json' argument specified multiple times")); + } + no_json = true; + } else if meta.path.is_ident("no_register") { + if no_register { + return Err(meta.error("'no_register' argument specified multiple times")); + } + no_register = true; + } else if meta.path.is_ident("deprecated_aliases") { + if !deprecated_aliases.is_empty() { + return Err( + meta.error("'deprecated_aliases' argument specified multiple times") + ); + } + meta.input.parse::()?; + // Parse array of string literals + let content; + syn::bracketed!(content in meta.input); + let aliases = content.parse_terminated( + |input: ParseStream| input.parse::(), + Token![,], + )?; + deprecated_aliases.extend(aliases.into_iter().map(|lit| lit.value())); + } else if meta.path.is_ident("deprecated") { + if deprecated.is_some() { + return Err(meta.error("'deprecated' argument specified multiple times")); + } + meta.input.parse::()?; + let lit: LitStr = meta.input.parse()?; + deprecated = Some(lit.value()); + } else { + return Err(meta.error(format!( + "'{:?}' argument not recognized, expected \ + 'namespace', 'no_json', 'no_register, 'deprecated_aliases', or 'deprecated'", + meta.path + ))); + } + Ok(()) + }) + .unwrap_or_else(|e| panic!("in #[action] attribute: {}", e)); + } + } + + let name = name_argument.unwrap_or_else(|| struct_name.to_string()); + + if name.contains("::") { + panic!( + "in #[action] attribute: `name = \"{name}\"` must not contain `::`, \ + also specify `namespace` instead" + ); + } + + let full_name = if let Some(namespace) = namespace { + format!("{namespace}::{name}") + } else { + name + }; + + let is_unit_struct = matches!(&input.data, Data::Struct(data) if data.fields.is_empty()); + + let build_fn_body = if no_json { + let error_msg = format!("{} cannot be built from JSON", full_name); + quote! { Err(gpui::private::anyhow::anyhow!(#error_msg)) } + } else if is_unit_struct { + quote! { Ok(Box::new(Self)) } + } else { + quote! { Ok(Box::new(gpui::private::serde_json::from_value::(_value)?)) } + }; + + let json_schema_fn_body = if no_json || is_unit_struct { + quote! { None } + } else { + quote! { Some(::json_schema(_generator)) } + }; + + let deprecated_aliases_fn_body = if deprecated_aliases.is_empty() { + quote! { &[] } + } else { + let aliases = deprecated_aliases.iter(); + quote! { &[#(#aliases),*] } + }; + + let deprecation_fn_body = if let Some(message) = deprecated { + quote! { Some(#message) } + } else { + quote! { None } + }; + + let registration = if no_register { + quote! {} + } else { + generate_register_action(struct_name) + }; + + TokenStream::from(quote! { + #registration + + impl gpui::Action for #struct_name { + fn name(&self) -> &'static str { + #full_name + } + + fn name_for_type() -> &'static str + where + Self: Sized + { + #full_name + } + + fn partial_eq(&self, action: &dyn gpui::Action) -> bool { + action + .as_any() + .downcast_ref::() + .map_or(false, |a| self == a) + } + + fn boxed_clone(&self) -> Box { + Box::new(self.clone()) + } + + fn build(_value: gpui::private::serde_json::Value) -> gpui::Result> { + #build_fn_body + } + + fn action_json_schema( + _generator: &mut gpui::private::schemars::r#gen::SchemaGenerator, + ) -> Option { + #json_schema_fn_body + } + + fn deprecated_aliases() -> &'static [&'static str] { + #deprecated_aliases_fn_body + } + + fn deprecation_message() -> Option<&'static str> { + #deprecation_fn_body + } + } + }) +} diff --git a/crates/gpui_macros/src/gpui_macros.rs b/crates/gpui_macros/src/gpui_macros.rs index 54c8e40d0f..3a58af6705 100644 --- a/crates/gpui_macros/src/gpui_macros.rs +++ b/crates/gpui_macros/src/gpui_macros.rs @@ -1,3 +1,4 @@ +mod derive_action; mod derive_app_context; mod derive_into_element; mod derive_render; @@ -12,12 +13,18 @@ mod derive_inspector_reflection; use proc_macro::TokenStream; use syn::{DeriveInput, Ident}; -/// register_action! can be used to register an action with the GPUI runtime. -/// You should typically use `gpui::actions!` or `gpui::impl_actions!` instead, -/// but this can be used for fine grained customization. +/// `Action` derive macro - see the trait documentation for details. +#[proc_macro_derive(Action, attributes(action))] +pub fn derive_action(input: TokenStream) -> TokenStream { + derive_action::derive_action(input) +} + +/// This can be used to register an action with the GPUI runtime when you want to manually implement +/// the `Action` trait. Typically you should use the `Action` derive macro or `actions!` macro +/// instead. #[proc_macro] pub fn register_action(ident: TokenStream) -> TokenStream { - register_action::register_action_macro(ident) + register_action::register_action(ident) } /// #[derive(IntoElement)] is used to create a Component out of anything that implements diff --git a/crates/gpui_macros/src/register_action.rs b/crates/gpui_macros/src/register_action.rs index 49a472eb10..d1910b82b2 100644 --- a/crates/gpui_macros/src/register_action.rs +++ b/crates/gpui_macros/src/register_action.rs @@ -1,18 +1,18 @@ use proc_macro::TokenStream; -use proc_macro2::Ident; +use proc_macro2::{Ident, TokenStream as TokenStream2}; use quote::{format_ident, quote}; use syn::parse_macro_input; -pub fn register_action_macro(ident: TokenStream) -> TokenStream { +pub(crate) fn register_action(ident: TokenStream) -> TokenStream { let name = parse_macro_input!(ident as Ident); - let registration = register_action(&name); + let registration = generate_register_action(&name); TokenStream::from(quote! { #registration }) } -pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream { +pub(crate) fn generate_register_action(type_name: &Ident) -> TokenStream2 { let action_builder_fn_name = format_ident!( "__gpui_actions_builder_{}", type_name.to_string().to_lowercase() @@ -28,11 +28,12 @@ pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream { #[doc(hidden)] fn #action_builder_fn_name() -> gpui::MacroActionData { gpui::MacroActionData { - name: <#type_name as gpui::Action>::debug_name(), - aliases: <#type_name as gpui::Action>::deprecated_aliases(), + name: <#type_name as gpui::Action>::name_for_type(), type_id: ::std::any::TypeId::of::<#type_name>(), build: <#type_name as gpui::Action>::build, json_schema: <#type_name as gpui::Action>::action_json_schema, + deprecated_aliases: <#type_name as gpui::Action>::deprecated_aliases(), + deprecation_message: <#type_name as gpui::Action>::deprecation_message(), } } @@ -41,7 +42,5 @@ pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream { } } } - - } } diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index e8b4e6a15f..eda4ae641f 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -9,10 +9,10 @@ use editor::{ scroll::Autoscroll, }; use gpui::{ - AnyElement, App, ClickEvent, Context, DismissEvent, Entity, EventEmitter, FocusHandle, + Action, AnyElement, App, ClickEvent, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Length, ListSizingBehavior, ListState, MouseButton, MouseUpEvent, Render, - ScrollStrategy, Stateful, Task, UniformListScrollHandle, Window, actions, div, impl_actions, - list, prelude::*, uniform_list, + ScrollStrategy, Stateful, Task, UniformListScrollHandle, Window, actions, div, list, + prelude::*, uniform_list, }; use head::Head; use schemars::JsonSchema; @@ -38,14 +38,13 @@ actions!(picker, [ConfirmCompletion]); /// ConfirmInput is an alternative editor action which - instead of selecting active picker entry - treats pickers editor input literally, /// performing some kind of action on it. -#[derive(Clone, PartialEq, Deserialize, JsonSchema, Default)] +#[derive(Clone, PartialEq, Deserialize, JsonSchema, Default, Action)] +#[action(namespace = picker)] #[serde(deny_unknown_fields)] pub struct ConfirmInput { pub secondary: bool, } -impl_actions!(picker, [ConfirmInput]); - struct PendingUpdateMatches { delegate_update_matches: Option>, _task: Task>, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 4243285e83..3bcc881f9d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -23,7 +23,7 @@ use gpui::{ ListSizingBehavior, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, ScrollStrategy, Stateful, Styled, Subscription, Task, UniformListScrollHandle, WeakEntity, Window, actions, anchored, deferred, - div, impl_actions, point, px, size, transparent_white, uniform_list, + div, point, px, size, transparent_white, uniform_list, }; use indexmap::IndexMap; use language::DiagnosticSeverity; @@ -181,22 +181,22 @@ struct EntryDetails { canonical_path: Option>, } -#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = project_panel)] #[serde(deny_unknown_fields)] struct Delete { #[serde(default)] pub skip_prompt: bool, } -#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = project_panel)] #[serde(deny_unknown_fields)] struct Trash { #[serde(default)] pub skip_prompt: bool, } -impl_actions!(project_panel, [Delete, Trash]); - actions!( project_panel, [ diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index d1b4d7518a..fa7a3ba915 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -16,7 +16,7 @@ use futures::channel::oneshot; use gpui::{ Action, App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, - Styled, Subscription, Task, TextStyle, Window, actions, div, impl_actions, + Styled, Subscription, Task, TextStyle, Window, actions, div, }; use language::{Language, LanguageRegistry}; use project::{ @@ -46,7 +46,8 @@ use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults}; const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50; -#[derive(PartialEq, Clone, Deserialize, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, JsonSchema, Action)] +#[action(namespace = buffer_search)] #[serde(deny_unknown_fields)] pub struct Deploy { #[serde(default = "util::serde::default_true")] @@ -57,8 +58,6 @@ pub struct Deploy { pub selection_search_enabled: bool, } -impl_actions!(buffer_search, [Deploy]); - actions!(buffer_search, [DeployReplace, Dismiss, FocusEditor]); impl Deploy { diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index 8bf5c0bd46..96736f512a 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -3,7 +3,7 @@ use collections::{BTreeMap, HashMap, IndexMap}; use fs::Fs; use gpui::{ Action, ActionBuildError, App, InvalidKeystrokeError, KEYSTROKE_PARSE_EXPECTED_MESSAGE, - KeyBinding, KeyBindingContextPredicate, NoAction, SharedString, + KeyBinding, KeyBindingContextPredicate, NoAction, }; use schemars::{ JsonSchema, @@ -414,14 +414,21 @@ impl KeymapFile { .into_generator(); let action_schemas = cx.action_schemas(&mut generator); - let deprecations = cx.action_deprecations(); - KeymapFile::generate_json_schema(generator, action_schemas, deprecations) + let deprecations = cx.deprecated_actions_to_preferred_actions(); + let deprecation_messages = cx.action_deprecation_messages(); + KeymapFile::generate_json_schema( + generator, + action_schemas, + deprecations, + deprecation_messages, + ) } fn generate_json_schema( generator: SchemaGenerator, - action_schemas: Vec<(SharedString, Option)>, - deprecations: &HashMap, + action_schemas: Vec<(&'static str, Option)>, + deprecations: &HashMap<&'static str, &'static str>, + deprecation_messages: &HashMap<&'static str, &'static str>, ) -> serde_json::Value { fn set(input: I) -> Option where @@ -492,9 +499,9 @@ impl KeymapFile { }; let mut keymap_action_alternatives = vec![plain_action.into(), action_with_input.into()]; - for (name, action_schema) in action_schemas.iter() { + for (name, action_schema) in action_schemas.into_iter() { let schema = if let Some(Schema::Object(schema)) = action_schema { - Some(schema.clone()) + Some(schema) } else { None }; @@ -509,7 +516,7 @@ impl KeymapFile { let deprecation = if name == NoAction.name() { Some("null") } else { - deprecations.get(name).map(|new_name| new_name.as_ref()) + deprecations.get(name).copied() }; // Add an alternative for plain action names. @@ -518,7 +525,9 @@ impl KeymapFile { const_value: Some(Value::String(name.to_string())), ..Default::default() }; - if let Some(new_name) = deprecation { + if let Some(message) = deprecation_messages.get(name) { + add_deprecation(&mut plain_action, message.to_string()); + } else if let Some(new_name) = deprecation { add_deprecation_preferred_name(&mut plain_action, new_name); } if let Some(description) = description.clone() { @@ -538,9 +547,11 @@ impl KeymapFile { ..Default::default() }; if let Some(description) = description.clone() { - add_description(&mut matches_action_name, description.to_string()); + add_description(&mut matches_action_name, description); } - if let Some(new_name) = deprecation { + if let Some(message) = deprecation_messages.get(name) { + add_deprecation(&mut matches_action_name, message.to_string()); + } else if let Some(new_name) = deprecation { add_deprecation_preferred_name(&mut matches_action_name, new_name); } let action_with_input = SchemaObject { diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index da57845c61..dd6626a716 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -8,8 +8,7 @@ use editor::EditorSettingsControls; use feature_flags::{FeatureFlag, FeatureFlagViewExt}; use fs::Fs; use gpui::{ - App, AsyncWindowContext, Entity, EventEmitter, FocusHandle, Focusable, Task, actions, - impl_actions, + Action, App, AsyncWindowContext, Entity, EventEmitter, FocusHandle, Focusable, Task, actions, }; use schemars::JsonSchema; use serde::Deserialize; @@ -27,19 +26,19 @@ impl FeatureFlag for SettingsUiFeatureFlag { const NAME: &'static str = "settings-ui"; } -#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] pub struct ImportVsCodeSettings { #[serde(default)] pub skip_prompt: bool, } -#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] pub struct ImportCursorSettings { #[serde(default)] pub skip_prompt: bool, } - -impl_actions!(zed, [ImportVsCodeSettings, ImportCursorSettings]); actions!(zed, [OpenSettingsEditor]); pub fn init(cx: &mut App) { diff --git a/crates/tab_switcher/src/tab_switcher.rs b/crates/tab_switcher/src/tab_switcher.rs index 7ba0d8d4c4..f2fa7b8b69 100644 --- a/crates/tab_switcher/src/tab_switcher.rs +++ b/crates/tab_switcher/src/tab_switcher.rs @@ -7,7 +7,7 @@ use fuzzy::StringMatchCandidate; use gpui::{ Action, AnyElement, App, Context, DismissEvent, Entity, EntityId, EventEmitter, FocusHandle, Focusable, Modifiers, ModifiersChangedEvent, MouseButton, MouseUpEvent, ParentElement, Render, - Styled, Task, WeakEntity, Window, actions, impl_actions, rems, + Styled, Task, WeakEntity, Window, actions, rems, }; use picker::{Picker, PickerDelegate}; use project::Project; @@ -25,14 +25,13 @@ use workspace::{ const PANEL_WIDTH_REMS: f32 = 28.; -#[derive(PartialEq, Clone, Deserialize, JsonSchema, Default)] +#[derive(PartialEq, Clone, Deserialize, JsonSchema, Default, Action)] +#[action(namespace = tab_switcher)] #[serde(deny_unknown_fields)] pub struct Toggle { #[serde(default)] pub select_last: bool, } - -impl_actions!(tab_switcher, [Toggle]); actions!(tab_switcher, [CloseSelectedItem, ToggleAll]); pub struct TabSwitcher { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 148d74ec2e..23202ef691 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -8,10 +8,10 @@ pub mod terminal_tab_tooltip; use assistant_slash_command::SlashCommandRegistry; use editor::{Editor, EditorSettings, actions::SelectAll, scroll::ScrollbarAutoHide}; use gpui::{ - AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, KeyContext, - KeyDownEvent, Keystroke, MouseButton, MouseDownEvent, Pixels, Render, ScrollWheelEvent, - Stateful, Styled, Subscription, Task, WeakEntity, actions, anchored, deferred, div, - impl_actions, + Action, AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, + KeyContext, KeyDownEvent, Keystroke, MouseButton, MouseDownEvent, Pixels, Render, + ScrollWheelEvent, Stateful, Styled, Subscription, Task, WeakEntity, actions, anchored, + deferred, div, }; use itertools::Itertools; use persistence::TERMINAL_DB; @@ -70,16 +70,16 @@ const GIT_DIFF_PATH_PREFIXES: &[&str] = &["a", "b"]; #[derive(Clone, Debug, PartialEq)] pub struct ScrollTerminal(pub i32); -#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = terminal)] pub struct SendText(String); -#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = terminal)] pub struct SendKeystroke(String); actions!(terminal, [RerunTask]); -impl_actions!(terminal, [SendText, SendKeystroke]); - pub fn init(cx: &mut App) { assistant_slash_command::init(cx); terminal_panel::init(cx); diff --git a/crates/title_bar/src/application_menu.rs b/crates/title_bar/src/application_menu.rs index 5ce5bd1599..58efa4ee3e 100644 --- a/crates/title_bar/src/application_menu.rs +++ b/crates/title_bar/src/application_menu.rs @@ -1,7 +1,7 @@ use gpui::{Entity, OwnedMenu, OwnedMenuItem}; #[cfg(not(target_os = "macos"))] -use gpui::{actions, impl_actions}; +use gpui::{Action, actions}; #[cfg(not(target_os = "macos"))] use schemars::JsonSchema; @@ -11,14 +11,12 @@ use serde::Deserialize; use smallvec::SmallVec; use ui::{ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*}; -#[cfg(not(target_os = "macos"))] -impl_actions!(app_menu, [OpenApplicationMenu]); - #[cfg(not(target_os = "macos"))] actions!(app_menu, [ActivateMenuRight, ActivateMenuLeft]); #[cfg(not(target_os = "macos"))] -#[derive(Clone, Deserialize, JsonSchema, PartialEq, Default)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Default, Action)] +#[action(namespace = app_menu)] pub struct OpenApplicationMenu(String); #[cfg(not(target_os = "macos"))] diff --git a/crates/title_bar/src/collab.rs b/crates/title_bar/src/collab.rs index 93533903a3..dbef8e02bf 100644 --- a/crates/title_bar/src/collab.rs +++ b/crates/title_bar/src/collab.rs @@ -11,10 +11,7 @@ use workspace::notifications::DetachAndPromptErr; use crate::TitleBar; -actions!( - collab, - [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall] -); +actions!(collab, [ToggleScreenSharing, ToggleMute, ToggleDeafen]); fn toggle_screen_sharing(_: &ToggleScreenSharing, window: &mut Window, cx: &mut App) { let call = ActiveCall::global(cx).read(cx); diff --git a/crates/ui/src/components/stories/context_menu.rs b/crates/ui/src/components/stories/context_menu.rs index c85785071b..b34c65a89b 100644 --- a/crates/ui/src/components/stories/context_menu.rs +++ b/crates/ui/src/components/stories/context_menu.rs @@ -4,7 +4,7 @@ use story::Story; use crate::prelude::*; use crate::{ContextMenu, Label, right_click_menu}; -actions!(context_menu, [PrintCurrentDate, PrintBestFood]); +actions!(stories, [PrintCurrentDate, PrintBestFood]); fn build_menu( window: &mut Window, diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 6f0a10964a..40e8fcffa3 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -7,7 +7,7 @@ use editor::{ display_map::ToDisplayPoint, scroll::Autoscroll, }; -use gpui::{Action, App, AppContext as _, Context, Global, Window, actions, impl_internal_actions}; +use gpui::{Action, App, AppContext as _, Context, Global, Window, actions}; use itertools::Itertools; use language::Point; use multi_buffer::MultiBufferRow; @@ -45,24 +45,28 @@ use crate::{ visual::VisualDeleteLine, }; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Action)] +#[action(namespace = vim, no_json, no_register)] pub struct GoToLine { range: CommandRange, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Action)] +#[action(namespace = vim, no_json, no_register)] pub struct YankCommand { range: CommandRange, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Action)] +#[action(namespace = vim, no_json, no_register)] pub struct WithRange { restore_selection: bool, range: CommandRange, action: WrappedAction, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Action)] +#[action(namespace = vim, no_json, no_register)] pub struct WithCount { count: u32, action: WrappedAction, @@ -152,21 +156,21 @@ impl VimOption { } } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, PartialEq, Action)] +#[action(namespace = vim, no_json, no_register)] pub struct VimSet { options: Vec, } -#[derive(Debug)] -struct WrappedAction(Box); - -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, PartialEq, Action)] +#[action(namespace = vim, no_json, no_register)] struct VimSave { pub save_intent: Option, pub filename: String, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, PartialEq, Action)] +#[action(namespace = vim, no_json, no_register)] enum DeleteMarks { Marks(String), AllLocal, @@ -176,26 +180,14 @@ actions!( vim, [VisualCommand, CountCommand, ShellCommand, ArgumentRequired] ); -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, PartialEq, Action)] +#[action(namespace = vim, no_json, no_register)] struct VimEdit { pub filename: String, } -impl_internal_actions!( - vim, - [ - GoToLine, - YankCommand, - WithRange, - WithCount, - OnMatchingLines, - ShellExec, - VimSet, - VimSave, - DeleteMarks, - VimEdit, - ] -); +#[derive(Debug)] +struct WrappedAction(Box); impl PartialEq for WrappedAction { fn eq(&self, other: &Self) -> bool { @@ -1289,7 +1281,8 @@ fn generate_positions(string: &str, query: &str) -> Vec { positions } -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Action)] +#[action(namespace = vim, no_json, no_register)] pub(crate) struct OnMatchingLines { range: CommandRange, search: String, @@ -1481,7 +1474,8 @@ impl OnMatchingLines { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Action)] +#[action(namespace = vim, no_json, no_register)] pub struct ShellExec { command: String, range: Option, diff --git a/crates/vim/src/digraph.rs b/crates/vim/src/digraph.rs index 9852b51ade..881454392a 100644 --- a/crates/vim/src/digraph.rs +++ b/crates/vim/src/digraph.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use collections::HashMap; use editor::Editor; -use gpui::{App, Context, Keystroke, KeystrokeEvent, Window, impl_actions}; +use gpui::{Action, App, Context, Keystroke, KeystrokeEvent, Window}; use schemars::JsonSchema; use serde::Deserialize; use settings::Settings; @@ -12,9 +12,9 @@ use crate::{Vim, VimSettings, state::Operator}; mod default; -#[derive(Debug, Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] struct Literal(String, char); -impl_actions!(vim, [Literal]); pub(crate) fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, Vim::literal) diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 29ed528a5e..6b92246e50 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -6,7 +6,7 @@ use editor::{ }, scroll::Autoscroll, }; -use gpui::{Context, Window, action_with_deprecated_aliases, actions, impl_actions, px}; +use gpui::{Action, Context, Window, actions, px}; use language::{CharKind, Point, Selection, SelectionGoal}; use multi_buffer::MultiBufferRow; use schemars::JsonSchema; @@ -177,147 +177,143 @@ enum IndentType { Same, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct NextWordStart { #[serde(default)] ignore_punctuation: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct NextWordEnd { #[serde(default)] ignore_punctuation: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct PreviousWordStart { #[serde(default)] ignore_punctuation: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct PreviousWordEnd { #[serde(default)] ignore_punctuation: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] pub(crate) struct NextSubwordStart { #[serde(default)] pub(crate) ignore_punctuation: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] pub(crate) struct NextSubwordEnd { #[serde(default)] pub(crate) ignore_punctuation: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] pub(crate) struct PreviousSubwordStart { #[serde(default)] pub(crate) ignore_punctuation: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] pub(crate) struct PreviousSubwordEnd { #[serde(default)] pub(crate) ignore_punctuation: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] pub(crate) struct Up { #[serde(default)] pub(crate) display_lines: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] pub(crate) struct Down { #[serde(default)] pub(crate) display_lines: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct FirstNonWhitespace { #[serde(default)] display_lines: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct EndOfLine { #[serde(default)] display_lines: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] pub struct StartOfLine { #[serde(default)] pub(crate) display_lines: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct MiddleOfLine { #[serde(default)] display_lines: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct UnmatchedForward { #[serde(default)] char: char, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct UnmatchedBackward { #[serde(default)] char: char, } -impl_actions!( - vim, - [ - StartOfLine, - MiddleOfLine, - EndOfLine, - FirstNonWhitespace, - Down, - Up, - NextWordStart, - NextWordEnd, - PreviousWordStart, - PreviousWordEnd, - NextSubwordStart, - NextSubwordEnd, - PreviousSubwordStart, - PreviousSubwordEnd, - UnmatchedForward, - UnmatchedBackward - ] -); - actions!( vim, [ Left, - Backspace, + #[action(deprecated_aliases = ["vim::Backspace"])] + WrappingLeft, Right, - Space, + #[action(deprecated_aliases = ["vim::Space"])] + WrappingRight, CurrentLine, SentenceForward, SentenceBackward, @@ -356,9 +352,6 @@ actions!( ] ); -action_with_deprecated_aliases!(vim, WrappingLeft, ["vim::Backspace"]); -action_with_deprecated_aliases!(vim, WrappingRight, ["vim::Space"]); - pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, |vim, _: &Left, window, cx| { vim.motion(Motion::Left, window, cx) @@ -366,10 +359,6 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, |vim, _: &WrappingLeft, window, cx| { vim.motion(Motion::WrappingLeft, window, cx) }); - // Deprecated. - Vim::action(editor, cx, |vim, _: &Backspace, window, cx| { - vim.motion(Motion::WrappingLeft, window, cx) - }); Vim::action(editor, cx, |vim, action: &Down, window, cx| { vim.motion( Motion::Down { @@ -394,10 +383,6 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, |vim, _: &WrappingRight, window, cx| { vim.motion(Motion::WrappingRight, window, cx) }); - // Deprecated. - Vim::action(editor, cx, |vim, _: &Space, window, cx| { - vim.motion(Motion::WrappingRight, window, cx) - }); Vim::action( editor, cx, diff --git a/crates/vim/src/normal/increment.rs b/crates/vim/src/normal/increment.rs index e092249e32..e2a0d28267 100644 --- a/crates/vim/src/normal/increment.rs +++ b/crates/vim/src/normal/increment.rs @@ -1,5 +1,5 @@ use editor::{Editor, MultiBufferSnapshot, ToOffset, ToPoint, scroll::Autoscroll}; -use gpui::{Context, Window, impl_actions}; +use gpui::{Action, Context, Window}; use language::{Bias, Point}; use schemars::JsonSchema; use serde::Deserialize; @@ -9,22 +9,22 @@ use crate::{Vim, state::Mode}; const BOOLEAN_PAIRS: &[(&str, &str)] = &[("true", "false"), ("yes", "no"), ("on", "off")]; -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct Increment { #[serde(default)] step: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct Decrement { #[serde(default)] step: bool, } -impl_actions!(vim, [Increment, Decrement]); - pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, |vim, action: &Increment, window, cx| { vim.record_current_action(cx); diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index ca8519ee21..41337f0707 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -1,5 +1,5 @@ use editor::{DisplayPoint, RowExt, display_map::ToDisplayPoint, movement, scroll::Autoscroll}; -use gpui::{Context, Window, impl_actions}; +use gpui::{Action, Context, Window}; use language::{Bias, SelectionGoal}; use schemars::JsonSchema; use serde::Deserialize; @@ -14,7 +14,8 @@ use crate::{ state::{Mode, Register}, }; -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] pub struct Paste { #[serde(default)] @@ -23,8 +24,6 @@ pub struct Paste { preserve_clipboard: bool, } -impl_actions!(vim, [Paste]); - impl Vim { pub fn paste(&mut self, action: &Paste, window: &mut Window, cx: &mut Context) { self.record_current_action(cx); diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 1c45e6de4c..6457798833 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -1,5 +1,5 @@ use editor::{Editor, EditorSettings}; -use gpui::{Context, Window, actions, impl_actions, impl_internal_actions}; +use gpui::{Action, Context, Window, actions}; use language::Point; use schemars::JsonSchema; use search::{BufferSearchBar, SearchOptions, buffer_search}; @@ -16,7 +16,8 @@ use crate::{ state::{Mode, SearchState}, }; -#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] pub(crate) struct MoveToNext { #[serde(default = "default_true")] @@ -27,7 +28,8 @@ pub(crate) struct MoveToNext { regex: bool, } -#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] pub(crate) struct MoveToPrevious { #[serde(default = "default_true")] @@ -38,7 +40,8 @@ pub(crate) struct MoveToPrevious { regex: bool, } -#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] pub(crate) struct Search { #[serde(default)] @@ -47,14 +50,16 @@ pub(crate) struct Search { regex: bool, } -#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] pub struct FindCommand { pub query: String, pub backwards: bool, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Action)] +#[action(namespace = vim, no_json, no_register)] pub struct ReplaceCommand { pub(crate) range: CommandRange, pub(crate) replacement: Replacement, @@ -69,8 +74,6 @@ pub(crate) struct Replacement { } actions!(vim, [SearchSubmit, MoveToNextMatch, MoveToPreviousMatch]); -impl_actions!(vim, [FindCommand, Search, MoveToPrevious, MoveToNext]); -impl_internal_actions!(vim, [ReplaceCommand]); pub(crate) fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, Vim::move_to_next); diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 7ce3dbe4c6..2486619608 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -10,7 +10,7 @@ use editor::{ display_map::{DisplaySnapshot, ToDisplayPoint}, movement::{self, FindRange}, }; -use gpui::{Window, actions, impl_actions}; +use gpui::{Action, Window, actions}; use itertools::Itertools; use language::{BufferSnapshot, CharKind, Point, Selection, TextObject, TreeSitterOptions}; use multi_buffer::MultiBufferRow; @@ -46,20 +46,23 @@ pub enum Object { EntireFile, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct Word { #[serde(default)] ignore_punctuation: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct Subword { #[serde(default)] ignore_punctuation: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct IndentObj { #[serde(default)] @@ -252,8 +255,6 @@ fn find_mini_brackets( find_mini_delimiters(map, display_point, around, &is_bracket_delimiter) } -impl_actions!(vim, [Word, Subword, IndentObj]); - actions!( vim, [ diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 88ce2138bc..6447300ed4 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -27,7 +27,7 @@ use editor::{ }; use gpui::{ Action, App, AppContext, Axis, Context, Entity, EventEmitter, KeyContext, KeystrokeEvent, - Render, Subscription, Task, WeakEntity, Window, actions, impl_actions, + Render, Subscription, Task, WeakEntity, Window, actions, }; use insert::{NormalBefore, TemporaryNormal}; use language::{CharKind, CursorShape, Point, Selection, SelectionGoal, TransactionId}; @@ -52,65 +52,77 @@ use crate::state::ReplayableAction; /// Number is used to manage vim's count. Pushing a digit /// multiplies the current value by 10 and adds the digit. -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] struct Number(usize); -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] struct SelectRegister(String); -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct PushObject { around: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct PushFindForward { before: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct PushFindBackward { after: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct PushSneak { first_char: Option, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct PushSneakBackward { first_char: Option, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] -struct PushAddSurrounds {} +struct PushAddSurrounds; -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct PushChangeSurrounds { target: Option, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct PushJump { line: bool, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct PushDigraph { first_char: Option, } -#[derive(Clone, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)] +#[action(namespace = vim)] #[serde(deny_unknown_fields)] struct PushLiteral { prefix: Option, @@ -169,24 +181,6 @@ actions!( // in the workspace namespace so it's not filtered out when vim is disabled. actions!(workspace, [ToggleVimMode,]); -impl_actions!( - vim, - [ - Number, - SelectRegister, - PushObject, - PushFindForward, - PushFindBackward, - PushSneak, - PushSneakBackward, - PushAddSurrounds, - PushChangeSurrounds, - PushJump, - PushDigraph, - PushLiteral - ] -); - /// Initializes the `vim` crate. pub fn init(cx: &mut App) { vim_mode_setting::init(cx); diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 11b8296d75..66336c7be6 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -955,7 +955,7 @@ pub mod test { pub focus_handle: FocusHandle, pub size: Pixels, } - actions!(test, [ToggleTestPanel]); + actions!(test_only, [ToggleTestPanel]); impl EventEmitter for TestPanel {} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 940c9eb04f..5fd04a556c 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -20,7 +20,7 @@ use gpui::{ DragMoveEvent, Entity, EntityId, EventEmitter, ExternalPaths, FocusHandle, FocusOutEvent, Focusable, KeyContext, MouseButton, MouseDownEvent, NavigationDirection, Pixels, Point, PromptLevel, Render, ScrollHandle, Subscription, Task, WeakEntity, WeakFocusHandle, Window, - actions, anchored, deferred, impl_actions, prelude::*, + actions, anchored, deferred, prelude::*, }; use itertools::Itertools; use language::DiagnosticSeverity; @@ -95,10 +95,12 @@ pub enum SaveIntent { Skip, } -#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)] +#[action(namespace = pane)] pub struct ActivateItem(pub usize); -#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)] +#[action(namespace = pane)] #[serde(deny_unknown_fields)] pub struct CloseActiveItem { pub save_intent: Option, @@ -106,7 +108,8 @@ pub struct CloseActiveItem { pub close_pinned: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)] +#[action(namespace = pane)] #[serde(deny_unknown_fields)] pub struct CloseInactiveItems { pub save_intent: Option, @@ -114,7 +117,8 @@ pub struct CloseInactiveItems { pub close_pinned: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)] +#[action(namespace = pane)] #[serde(deny_unknown_fields)] pub struct CloseAllItems { pub save_intent: Option, @@ -122,35 +126,40 @@ pub struct CloseAllItems { pub close_pinned: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)] +#[action(namespace = pane)] #[serde(deny_unknown_fields)] pub struct CloseCleanItems { #[serde(default)] pub close_pinned: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)] +#[action(namespace = pane)] #[serde(deny_unknown_fields)] pub struct CloseItemsToTheRight { #[serde(default)] pub close_pinned: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)] +#[action(namespace = pane)] #[serde(deny_unknown_fields)] pub struct CloseItemsToTheLeft { #[serde(default)] pub close_pinned: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)] +#[action(namespace = pane)] #[serde(deny_unknown_fields)] pub struct RevealInProjectPanel { #[serde(skip)] pub entry_id: Option, } -#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)] +#[action(namespace = pane)] #[serde(deny_unknown_fields)] pub struct DeploySearch { #[serde(default)] @@ -161,21 +170,6 @@ pub struct DeploySearch { pub excluded_files: Option, } -impl_actions!( - pane, - [ - CloseAllItems, - CloseActiveItem, - CloseCleanItems, - CloseItemsToTheLeft, - CloseItemsToTheRight, - CloseInactiveItems, - ActivateItem, - RevealInProjectPanel, - DeploySearch, - ] -); - actions!( pane, [ diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 3fdfd0e2ac..f9a25b2018 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -39,8 +39,8 @@ use gpui::{ CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle, Focusable, Global, HitboxBehavior, Hsla, KeyContext, Keystroke, ManagedView, MouseButton, PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, Size, Stateful, Subscription, Task, - Tiling, WeakEntity, WindowBounds, WindowHandle, WindowId, WindowOptions, action_as, actions, - canvas, impl_action_as, impl_actions, point, relative, size, transparent_black, + Tiling, WeakEntity, WindowBounds, WindowHandle, WindowId, WindowOptions, actions, canvas, + point, relative, size, transparent_black, }; pub use history_manager::*; pub use item::{ @@ -213,10 +213,12 @@ pub struct OpenPaths { pub paths: Vec, } -#[derive(Clone, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Deserialize, PartialEq, JsonSchema, Action)] +#[action(namespace = workspace)] pub struct ActivatePane(pub usize); -#[derive(Clone, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Deserialize, PartialEq, JsonSchema, Action)] +#[action(namespace = workspace)] #[serde(deny_unknown_fields)] pub struct MoveItemToPane { pub destination: usize, @@ -226,7 +228,8 @@ pub struct MoveItemToPane { pub clone: bool, } -#[derive(Clone, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Deserialize, PartialEq, JsonSchema, Action)] +#[action(namespace = workspace)] #[serde(deny_unknown_fields)] pub struct MoveItemToPaneInDirection { pub direction: SplitDirection, @@ -236,65 +239,60 @@ pub struct MoveItemToPaneInDirection { pub clone: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = workspace)] #[serde(deny_unknown_fields)] pub struct SaveAll { pub save_intent: Option, } -#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = workspace)] #[serde(deny_unknown_fields)] pub struct Save { pub save_intent: Option, } -#[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)] +#[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = workspace)] #[serde(deny_unknown_fields)] pub struct CloseAllItemsAndPanes { pub save_intent: Option, } -#[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)] +#[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = workspace)] #[serde(deny_unknown_fields)] pub struct CloseInactiveTabsAndPanes { pub save_intent: Option, } -#[derive(Clone, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Deserialize, PartialEq, JsonSchema, Action)] +#[action(namespace = workspace)] pub struct SendKeystrokes(pub String); -#[derive(Clone, Deserialize, PartialEq, Default, JsonSchema)] +#[derive(Clone, Deserialize, PartialEq, Default, JsonSchema, Action)] +#[action(namespace = workspace)] #[serde(deny_unknown_fields)] pub struct Reload { pub binary_path: Option, } -action_as!(project_symbols, ToggleProjectSymbols as Toggle); +actions!( + project_symbols, + [ + #[action(name = "Toggle")] + ToggleProjectSymbols + ] +); -#[derive(Default, PartialEq, Eq, Clone, Deserialize, JsonSchema)] +#[derive(Default, PartialEq, Eq, Clone, Deserialize, JsonSchema, Action)] +#[action(namespace = file_finder, name = "Toggle")] pub struct ToggleFileFinder { #[serde(default)] pub separate_history: bool, } -impl_action_as!(file_finder, ToggleFileFinder as Toggle); - -impl_actions!( - workspace, - [ - ActivatePane, - CloseAllItemsAndPanes, - CloseInactiveTabsAndPanes, - MoveItemToPane, - MoveItemToPaneInDirection, - OpenTerminal, - Reload, - Save, - SaveAll, - SendKeystrokes, - ] -); - actions!( workspace, [ @@ -360,7 +358,8 @@ impl PartialEq for Toast { } } -#[derive(Debug, Default, Clone, Deserialize, PartialEq, JsonSchema)] +#[derive(Debug, Default, Clone, Deserialize, PartialEq, JsonSchema, Action)] +#[action(namespace = workspace)] #[serde(deny_unknown_fields)] pub struct OpenTerminal { pub working_directory: PathBuf, @@ -6492,6 +6491,11 @@ pub fn last_session_workspace_locations( actions!( collab, [ + /// Opens the channel notes for the current call. + /// + /// If you want to open a specific channel, use `zed::OpenZedUrl` with a channel notes URL - + /// can be copied via "Copy link to section" in the context menu of the channel notes + /// buffer. These URLs look like `https://zed.dev/channel/channel-name-CHANNEL_ID/notes`. OpenChannelNotes, Mute, Deafen, diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c6b65eeb0b..78854ea644 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -168,7 +168,9 @@ dap = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } image_viewer = { workspace = true, features = ["test-support"] } +itertools.workspace = true language = { workspace = true, features = ["test-support"] } +pretty_assertions.workspace = true project = { workspace = true, features = ["test-support"] } terminal_view = { workspace = true, features = ["test-support"] } tree-sitter-md.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 1f95d938a5..1b8b1d697d 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -1308,7 +1308,7 @@ fn dump_all_gpui_actions() { .map(|action| ActionDef { name: action.name, human_name: command_palette::humanize_action_name(action.name), - aliases: action.aliases, + aliases: action.deprecated_aliases, }) .collect::>(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b9e6523f73..52a03b0adb 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1762,6 +1762,7 @@ mod tests { TestAppContext, UpdateGlobal, VisualTestContext, WindowHandle, actions, }; use language::{LanguageMatcher, LanguageRegistry}; + use pretty_assertions::{assert_eq, assert_ne}; use project::{Project, ProjectPath, WorktreeSettings, project_settings::ProjectSettings}; use serde_json::json; use settings::{SettingsStore, watch_config_file}; @@ -3926,6 +3927,8 @@ mod tests { }) } + actions!(test_only, [ActionA, ActionB]); + #[gpui::test] async fn test_base_keymap(cx: &mut gpui::TestAppContext) { let executor = cx.executor(); @@ -3934,7 +3937,6 @@ mod tests { let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); - actions!(test1, [A, B]); // From the Atom keymap use workspace::ActivatePreviousPane; // From the JetBrains keymap @@ -3954,7 +3956,7 @@ mod tests { .fs .save( "/keymap.json".as_ref(), - &r#"[{"bindings": {"backspace": "test1::A"}}]"#.into(), + &r#"[{"bindings": {"backspace": "test_only::ActionA"}}]"#.into(), Default::default(), ) .await @@ -3981,8 +3983,8 @@ mod tests { }); workspace .update(cx, |workspace, _, cx| { - workspace.register_action(|_, _: &A, _window, _cx| {}); - workspace.register_action(|_, _: &B, _window, _cx| {}); + workspace.register_action(|_, _: &ActionA, _window, _cx| {}); + workspace.register_action(|_, _: &ActionB, _window, _cx| {}); workspace.register_action(|_, _: &ActivatePreviousPane, _window, _cx| {}); workspace.register_action(|_, _: &ActivatePreviousItem, _window, _cx| {}); cx.notify(); @@ -3993,7 +3995,7 @@ mod tests { assert_key_bindings_for( workspace.into(), cx, - vec![("backspace", &A), ("k", &ActivatePreviousPane)], + vec![("backspace", &ActionA), ("k", &ActivatePreviousPane)], line!(), ); @@ -4002,7 +4004,7 @@ mod tests { .fs .save( "/keymap.json".as_ref(), - &r#"[{"bindings": {"backspace": "test1::B"}}]"#.into(), + &r#"[{"bindings": {"backspace": "test_only::ActionB"}}]"#.into(), Default::default(), ) .await @@ -4013,7 +4015,7 @@ mod tests { assert_key_bindings_for( workspace.into(), cx, - vec![("backspace", &B), ("k", &ActivatePreviousPane)], + vec![("backspace", &ActionB), ("k", &ActivatePreviousPane)], line!(), ); @@ -4033,7 +4035,7 @@ mod tests { assert_key_bindings_for( workspace.into(), cx, - vec![("backspace", &B), ("{", &ActivatePreviousItem)], + vec![("backspace", &ActionB), ("{", &ActivatePreviousItem)], line!(), ); } @@ -4046,7 +4048,6 @@ mod tests { let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx)); - actions!(test2, [A, B]); // From the Atom keymap use workspace::ActivatePreviousPane; // From the JetBrains keymap @@ -4054,8 +4055,8 @@ mod tests { workspace .update(cx, |workspace, _, _| { - workspace.register_action(|_, _: &A, _window, _cx| {}); - workspace.register_action(|_, _: &B, _window, _cx| {}); + workspace.register_action(|_, _: &ActionA, _window, _cx| {}); + workspace.register_action(|_, _: &ActionB, _window, _cx| {}); workspace.register_action(|_, _: &Deploy, _window, _cx| {}); }) .unwrap(); @@ -4072,7 +4073,7 @@ mod tests { .fs .save( "/keymap.json".as_ref(), - &r#"[{"bindings": {"backspace": "test2::A"}}]"#.into(), + &r#"[{"bindings": {"backspace": "test_only::ActionA"}}]"#.into(), Default::default(), ) .await @@ -4106,7 +4107,7 @@ mod tests { assert_key_bindings_for( workspace.into(), cx, - vec![("backspace", &A), ("k", &ActivatePreviousPane)], + vec![("backspace", &ActionA), ("k", &ActivatePreviousPane)], line!(), ); @@ -4219,6 +4220,122 @@ mod tests { }); } + /// Checks that action namespaces are the expected set. The purpose of this is to prevent typos + /// and let you know when introducing a new namespace. + #[gpui::test] + async fn test_action_namespaces(cx: &mut gpui::TestAppContext) { + use itertools::Itertools; + + init_keymap_test(cx); + cx.update(|cx| { + let all_actions = cx.all_action_names(); + + let mut actions_without_namespace = Vec::new(); + let all_namespaces = all_actions + .iter() + .filter_map(|action_name| { + let namespace = action_name + .split("::") + .collect::>() + .into_iter() + .rev() + .skip(1) + .rev() + .join("::"); + if namespace.is_empty() { + actions_without_namespace.push(*action_name); + } + if &namespace == "test_only" || &namespace == "stories" { + None + } else { + Some(namespace) + } + }) + .sorted() + .dedup() + .collect::>(); + assert_eq!(actions_without_namespace, Vec::<&str>::new()); + + let expected_namespaces = vec![ + "activity_indicator", + "agent", + #[cfg(not(target_os = "macos"))] + "app_menu", + "assistant", + "assistant2", + "auto_update", + "branches", + "buffer_search", + "channel_modal", + "chat_panel", + "cli", + "client", + "collab", + "collab_panel", + "command_palette", + "console", + "context_server", + "copilot", + "debug_panel", + "debugger", + "dev", + "diagnostics", + "edit_prediction", + "editor", + "feedback", + "file_finder", + "git", + "git_onboarding", + "git_panel", + "go_to_line", + "icon_theme_selector", + "jj", + "journal", + "language_selector", + "markdown", + "menu", + "notebook", + "notification_panel", + "outline", + "outline_panel", + "pane", + "panel", + "picker", + "project_panel", + "project_search", + "project_symbols", + "projects", + "repl", + "rules_library", + "search", + "snippets", + "supermaven", + "tab_switcher", + "task", + "terminal", + "terminal_panel", + "theme_selector", + "toast", + "toolchain", + "variable_list", + "vim", + "welcome", + "workspace", + "zed", + "zed_predict_onboarding", + "zeta", + ]; + assert_eq!( + all_namespaces, + expected_namespaces + .into_iter() + .map(|namespace| namespace.to_string()) + .sorted() + .collect::>() + ); + }); + } + #[gpui::test] fn test_bundled_settings_and_themes(cx: &mut App) { cx.text_system() diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 7dd29d72fc..b8c52e27e8 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -1,4 +1,4 @@ -use gpui::{actions, impl_actions}; +use gpui::{Action, actions}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -11,20 +11,20 @@ use serde::{Deserialize, Serialize}; // https://github.com/mmastrac/rust-ctor/issues/280 pub fn init() {} -#[derive(Clone, PartialEq, Deserialize, JsonSchema)] +#[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] #[serde(deny_unknown_fields)] pub struct OpenBrowser { pub url: String, } -#[derive(Clone, PartialEq, Deserialize, JsonSchema)] +#[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] #[serde(deny_unknown_fields)] pub struct OpenZedUrl { pub url: String, } -impl_actions!(zed, [OpenBrowser, OpenZedUrl]); - actions!( zed, [ @@ -56,62 +56,56 @@ pub enum ExtensionCategoryFilter { DebugAdapters, } -#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] pub struct Extensions { /// Filters the extensions page down to extensions that are in the specified category. #[serde(default)] pub category_filter: Option, } -#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] pub struct DecreaseBufferFontSize { #[serde(default)] pub persist: bool, } -#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] pub struct IncreaseBufferFontSize { #[serde(default)] pub persist: bool, } -#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] pub struct ResetBufferFontSize { #[serde(default)] pub persist: bool, } -#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] pub struct DecreaseUiFontSize { #[serde(default)] pub persist: bool, } -#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] pub struct IncreaseUiFontSize { #[serde(default)] pub persist: bool, } -#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] +#[action(namespace = zed)] pub struct ResetUiFontSize { #[serde(default)] pub persist: bool, } -impl_actions!( - zed, - [ - Extensions, - DecreaseBufferFontSize, - IncreaseBufferFontSize, - ResetBufferFontSize, - DecreaseUiFontSize, - IncreaseUiFontSize, - ResetUiFontSize, - ] -); - pub mod dev { use gpui::actions; @@ -119,34 +113,32 @@ pub mod dev { } pub mod workspace { - use gpui::action_with_deprecated_aliases; + use gpui::actions; - action_with_deprecated_aliases!( + actions!( workspace, - CopyPath, [ - "editor::CopyPath", - "outline_panel::CopyPath", - "project_panel::CopyPath" - ] - ); - - action_with_deprecated_aliases!( - workspace, - CopyRelativePath, - [ - "editor::CopyRelativePath", - "outline_panel::CopyRelativePath", - "project_panel::CopyRelativePath" + #[action(deprecated_aliases = ["editor::CopyPath", "outline_panel::CopyPath", "project_panel::CopyPath"])] + CopyPath, + #[action(deprecated_aliases = ["editor::CopyRelativePath", "outline_panel::CopyRelativePath", "project_panel::CopyRelativePath"])] + CopyRelativePath ] ); } pub mod git { - use gpui::{action_with_deprecated_aliases, actions}; + use gpui::actions; - actions!(git, [CheckoutBranch, Switch, SelectRepo]); - action_with_deprecated_aliases!(git, Branch, ["branches::OpenRecent"]); + actions!( + git, + [ + CheckoutBranch, + Switch, + SelectRepo, + #[action(deprecated_aliases = ["branches::OpenRecent"])] + Branch + ] + ); } pub mod jj { @@ -174,33 +166,31 @@ pub mod feedback { } pub mod theme_selector { - use gpui::impl_actions; + use gpui::Action; use schemars::JsonSchema; use serde::Deserialize; - #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] + #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] + #[action(namespace = theme_selector)] #[serde(deny_unknown_fields)] pub struct Toggle { /// A list of theme names to filter the theme selector down to. pub themes_filter: Option>, } - - impl_actions!(theme_selector, [Toggle]); } pub mod icon_theme_selector { - use gpui::impl_actions; + use gpui::Action; use schemars::JsonSchema; use serde::Deserialize; - #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] + #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] + #[action(namespace = icon_theme_selector)] #[serde(deny_unknown_fields)] pub struct Toggle { /// A list of icon theme names to filter the theme selector down to. pub themes_filter: Option>, } - - impl_actions!(icon_theme_selector, [Toggle]); } pub mod agent { @@ -213,40 +203,35 @@ pub mod agent { } pub mod assistant { - use gpui::{ - action_with_deprecated_aliases, actions, impl_action_with_deprecated_aliases, impl_actions, - }; + use gpui::{Action, actions}; use schemars::JsonSchema; use serde::Deserialize; use uuid::Uuid; - action_with_deprecated_aliases!(agent, ToggleFocus, ["assistant::ToggleFocus"]); + actions!( + agent, + [ + #[action(deprecated_aliases = ["assistant::ToggleFocus"])] + ToggleFocus + ] + ); actions!(assistant, [ShowConfiguration]); - #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] + #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)] + #[action(namespace = agent, deprecated_aliases = ["assistant::OpenRulesLibrary", "assistant::DeployPromptLibrary"])] #[serde(deny_unknown_fields)] pub struct OpenRulesLibrary { #[serde(skip)] pub prompt_to_select: Option, } - impl_action_with_deprecated_aliases!( - agent, - OpenRulesLibrary, - [ - "assistant::OpenRulesLibrary", - "assistant::DeployPromptLibrary" - ] - ); - - #[derive(Clone, Default, Deserialize, PartialEq, JsonSchema)] + #[derive(Clone, Default, Deserialize, PartialEq, JsonSchema, Action)] + #[action(namespace = assistant)] #[serde(deny_unknown_fields)] pub struct InlineAssist { pub prompt: Option, } - - impl_actions!(assistant, [InlineAssist]); } pub mod debugger { @@ -255,14 +240,16 @@ pub mod debugger { actions!(debugger, [OpenOnboardingModal, ResetOnboarding]); } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = projects)] #[serde(deny_unknown_fields)] pub struct OpenRecent { #[serde(default)] pub create_new_window: bool, } -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = projects)] #[serde(deny_unknown_fields)] pub struct OpenRemote { #[serde(default)] @@ -271,8 +258,6 @@ pub struct OpenRemote { pub create_new_window: bool, } -impl_actions!(projects, [OpenRecent, OpenRemote]); - /// Where to spawn the task in the UI. #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] @@ -285,7 +270,8 @@ pub enum RevealTarget { } /// Spawn a task with name or open tasks modal. -#[derive(Debug, PartialEq, Clone, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Deserialize, JsonSchema, Action)] +#[action(namespace = task)] #[serde(untagged)] pub enum Spawn { /// Spawns a task by the name given. @@ -317,7 +303,8 @@ impl Spawn { } /// Rerun the last task. -#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema, Action)] +#[action(namespace = task)] #[serde(deny_unknown_fields)] pub struct Rerun { /// Controls whether the task context is reevaluated prior to execution of a task. @@ -340,14 +327,18 @@ pub struct Rerun { pub task_id: Option, } -impl_actions!(task, [Spawn, Rerun]); - pub mod outline { use std::sync::OnceLock; - use gpui::{AnyView, App, Window, action_as}; + use gpui::{AnyView, App, Window, actions}; - action_as!(outline, ToggleOutline as Toggle); + actions!( + outline, + [ + #[action(name = "Toggle")] + ToggleOutline + ] + ); /// A pointer to outline::toggle function, exposed here to sewer the breadcrumbs <-> outline dependency. pub static TOGGLE_OUTLINE: OnceLock = OnceLock::new(); }