From 24c94d474e6c2786f676ad1d185caf445aaf522a Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Mon, 23 Jun 2025 22:34:51 -0600 Subject: [PATCH] gpui: Simplify `Action` macros + support doc comments in `actions!` (#33263) Instead of a menagerie of macros for implementing `Action`, now there are just two: * `actions!(editor, [MoveLeft, MoveRight])` * `#[derive(..., Action)]` with `#[action(namespace = editor)]` In both contexts, `///` doc comments can be provided and will be used in `JsonSchema`. In both contexts, parameters can provided 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. This is a new feature. Also makes the following changes since this seems like a good time to make breaking changes: * In `zed.rs` tests adds a test with an explicit list of namespaces. The rationale for this is that there is otherwise no checking of `namespace = ...` attributes. * `Action::debug_name` renamed to `name_for_type`, since its only difference with `name` was that it * `Action::name` now returns `&'static str` instead of `&str` to match the return of `name_for_type`. This makes the action trait more limited, but the code was already assuming that `name_for_type` is the same as `name`, and it requires `&'static`. So really this just makes the trait harder to misuse. * Various action reflection methods now use `&'static str` instead of `SharedString`. Release Notes: - N/A --- .rules | 4 +- Cargo.lock | 2 + crates/agent_ui/src/agent_ui.rs | 10 +- .../src/context_editor.rs | 11 +- .../src/language_model_selector.rs | 10 +- crates/docs_preprocessor/src/main.rs | 2 +- crates/editor/src/actions.rs | 132 +++--- crates/editor/src/editor.rs | 2 +- crates/git/src/git.rs | 15 +- crates/gpui/src/action.rs | 446 +++++------------- crates/gpui/src/app.rs | 19 +- crates/gpui/src/interactive.rs | 2 +- crates/gpui/src/key_dispatch.rs | 2 +- crates/gpui/src/keymap.rs | 4 +- crates/gpui/src/keymap/context.rs | 4 +- crates/gpui/tests/action_macros.rs | 22 +- crates/gpui_macros/src/derive_action.rs | 176 +++++++ crates/gpui_macros/src/gpui_macros.rs | 15 +- crates/gpui_macros/src/register_action.rs | 15 +- crates/picker/src/picker.rs | 11 +- crates/project_panel/src/project_panel.rs | 10 +- crates/search/src/buffer_search.rs | 7 +- crates/settings/src/keymap_file.rs | 33 +- crates/settings_ui/src/settings_ui.rs | 11 +- crates/tab_switcher/src/tab_switcher.rs | 7 +- crates/terminal_view/src/terminal_view.rs | 16 +- crates/title_bar/src/application_menu.rs | 8 +- crates/title_bar/src/collab.rs | 5 +- .../ui/src/components/stories/context_menu.rs | 2 +- crates/vim/src/command.rs | 52 +- crates/vim/src/digraph.rs | 6 +- crates/vim/src/motion.rs | 89 ++-- crates/vim/src/normal/increment.rs | 10 +- crates/vim/src/normal/paste.rs | 7 +- crates/vim/src/normal/search.rs | 19 +- crates/vim/src/object.rs | 13 +- crates/vim/src/vim.rs | 58 +-- crates/workspace/src/dock.rs | 2 +- crates/workspace/src/pane.rs | 44 +- crates/workspace/src/workspace.rs | 68 +-- crates/zed/Cargo.toml | 2 + crates/zed/src/main.rs | 2 +- crates/zed/src/zed.rs | 143 +++++- crates/zed_actions/src/lib.rs | 149 +++--- 44 files changed, 878 insertions(+), 789 deletions(-) create mode 100644 crates/gpui_macros/src/derive_action.rs 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(); }