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 | 24 +- 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, 879 insertions(+), 790 deletions(-) create mode 100644 crates/gpui_macros/src/derive_action.rs diff --git a/.rules b/.rules index b9eea27b67ee0c3b507f2bddbcbfbbb0a1fb696b..da009f1877b4c6ef2f0613995391852d4bf1dc8a 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 a4c34e87c1e189587dd00c87db573bdac82ce348..518b790a2c357be5aabb71d6c59a58dd7a0a6ffc 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 eee0ab19938e56378ae98c359d5f64644abf303b..0a9649e318d3e062dcc066d42565c0b65b0580cc 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 105778117ed55bcefa7f8587e49ecad9f35b36f9..57499bae6c4dd2684baea3b0d6202ef5c8681331 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 b1cf3a050c6f41daa018ec6a6878cc1919a058de..d9d11231edbe128fcfd9a486278ed8e1542b4397 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 8ec27a02a79180800d76cd1f483f91ee97d15c5e..c8e945c7e83564d162e0b939b92169b905558393 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 5a66c37bee7fe87587dbc5d50f65a7783dcaa7d7..b8a3e5efa778579b61b969e8c224de1bd237bbd2 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 4f391a7b4f763db87fa9d652f5f38d3091b88423..568e9062c86bb5b2b584bfeaa4f430c88d251a76 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 e2c6e54993639000f04d3f0f000c82952ea7b137..bb8f39f127f7bb3edae553844daf1635fb6b312d 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 db617758b37d2af13f5bad2ff803f387fd0e9d52..bfb37efd9a45e7e69781634a54c686548272b404 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. +/// +/// ``` +/// 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_derive::Deserialize)] +/// #[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 6c8b48873dd997a0b84ca3d43d911a0746d46d2d..109d5e7454c4a2b0bcb276243f7f5a6cc072efce 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 16cd8381cd1368a543d1f29037238bdbc78dca16..edd807da11410fa7255cd4613704a9444c197bb0 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 8e2af9422b163afa767e02d48f4b44961ded6bef..a290a132c3b5f9fa42e338c28b86de7ded5b10ac 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 ef088259de360d26d4c19c103511edf58fb235d1..b5dbab15c77a0cfff96885e5835f602197e408e6 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 ae6589e23afe4962fdd129696146504ed096f581..1221aa1224bcd9a541dd6461016b601939a15b28 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 5a76dfa9f3ea8e9d05579028847b93611ceb87d4..f601639fc8dbc05afaeed997a0c0b17c5dfa5ea7 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]); - - #[derive(PartialEq, Clone, Deserialize, JsonSchema)] - struct AnotherTestAction; - - impl_actions!(test, [AnotherTestAction]); + actions!( + test_only, + [ + SomeAction, + /// Documented action + SomeActionWithDocs, + ] + ); + + #[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 0000000000000000000000000000000000000000..c382ddd9c652902e1c444f080a601de16bfeca9a --- /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 54c8e40d0f116a5a008198bc954d1b6ca4684cdf..3a58af67052d06f108b4b9c87d52fc358405466e 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 49a472eb10a299cd6f64ea80215ee28bb293667e..d1910b82b2a7714849fc8d380dfc8b1b4e6b0d05 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 e8b4e6a15ff16a0d3bbb9eb9f46fc061e006e886..eda4ae641fc804d2b32a1980ce47824712c0a1a8 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 4243285e83b4dfb945dad03a1c1ce6c6c1ab2592..3bcc881f9d8a39ddbf1285e0deffe6b2907a4aa5 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 d1b4d7518ad00c67c5dc0d9fba6185afcf151971..fa7a3ba915896d52f1d2f60f55d5ab13746edda8 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 8bf5c0bd46157d88ac219902b028fd089cabfe11..96736f512a835772defb75be846559090764b4ca 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 da57845c61e3c9c400fd9e0aaeb9c04326d67f18..dd6626a7160fac48ccc3be8bb1387a166aef4692 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 7ba0d8d4c433c12cc19f022c9820910b75871704..f2fa7b8b699d69e1c915200b7a9ed8855e4e68f7 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 148d74ec2ea25f9b523543da9730fbf6b4af9c12..23202ef69166820896047b983fb79f770a4e7676 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 5ce5bd15994c2f91f380decf8b0e6cabf50962fc..58efa4ee3e3bd657e7c645f51861fa7ba524f63a 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 93533903a35fcd614defab9db11936e3f54515e7..dbef8e02bf3677a2857fd836d4bc1f8c62466337 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 c85785071b56fd96c8c824ab7ff49dc0194e8aeb..b34c65a89b75588163a6cb5887e0d6cb37257077 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 6f0a10964a6f53ee679a6f72698587bf0baa6f19..40e8fcffa3c90be95f1421548a19c3a1a444035c 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 9852b51ade20fe0b639d8f189caacc1ae467c814..881454392aca37a8534cfef3ffbc2410b7bb352b 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 29ed528a5e4d0c468c7e892d44b52d0cfc5ba500..6b92246e501092ed547ae7169ac0691f67f4a3a8 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 e092249e3243b4ab0297091bae3f55e442b3ff79..e2a0d282673a6f1ccb96d7c0a2d63f55d3dd78c1 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 ca8519ee21b61e736f51efee6ddbbe70a87f10bf..41337f07074e56e17b35bc72addf3c0ce3ae0f39 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 1c45e6de4ce82aca1d39c7221768a501e104aafb..645779883341e264fd72f41d889981f2275186a0 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 7ce3dbe4c68060d7329c27c773cad5c7bc187d83..2486619608fa8206d5bd7479ad93681b922081ec 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 88ce2138bc527be412c1a12486567cadc49b91f8..6447300ed40c9dacb501344244f417de2931afed 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 11b8296d75a1f941bc90e1bbf7d917b20a064636..66336c7be64b6c076fd014ae209ad0aaefecb623 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 940c9eb04ff0aeb06c63e7760cfc076251015fb5..5fd04a556cfc996b5616f3bde1989ef36f0e236d 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 3fdfd0e2ac22c3c9a5c57c364cd0041e065ee13c..f9a25b2018243c520934a8e666b9c1b177e8149d 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 c6b65eeb0b9ed2d993522c0c0337b15959d10344..78854ea6446c92c9a45adc4bd81b71fc73032a31 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 1f95d938a5d873e914d20db14507e721df388fe0..1b8b1d697d5e32a9e285c8a258598e14adcb73d1 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 b9e6523f736efbddf993d3edba23af64891c9e6c..52a03b0adbcf088a889cdb640bc2d732138d06e8 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 7dd29d72fc967bdeae295bdbbaa74dc5b88515a2..b8c52e27e83ffbdc152e94ea514f30b4c5df8223 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; - - action_with_deprecated_aliases!( - workspace, - CopyPath, - [ - "editor::CopyPath", - "outline_panel::CopyPath", - "project_panel::CopyPath" - ] - ); + use gpui::actions; - action_with_deprecated_aliases!( + actions!( 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(); }