Detailed changes
@@ -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`.
@@ -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",
@@ -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<ThreadId>,
}
-#[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<AgentProfileId>,
@@ -104,8 +106,6 @@ impl ManageProfiles {
}
}
-impl_actions!(agent, [NewThread, ManageProfiles]);
-
#[derive(Clone)]
pub(crate) enum ModelUsageContext {
Thread(Entity<Thread>),
@@ -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<ProjectPath>),
ExternalFiles(Vec<PathBuf>),
}
-impl_internal_actions!(assistant, [InsertDraggedFiles]);
-
#[derive(Copy, Clone, Debug, PartialEq)]
struct ScrollPosition {
offset_before_cursor: gpui::Point<f32>,
@@ -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
]
);
@@ -247,7 +247,7 @@ fn dump_all_gpui_actions() -> Vec<ActionDef> {
.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::<Vec<ActionDef>>();
@@ -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<usize>,
}
-#[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<usize>,
}
-#[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<usize>,
}
-#[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<String>,
}
-#[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"]);
@@ -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};
@@ -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;
@@ -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<Box<dyn Action>>;
pub(crate) struct ActionRegistry {
- by_name: HashMap<SharedString, ActionData>,
- names_by_type_id: HashMap<TypeId, SharedString>,
- all_names: Vec<SharedString>, // So we can return a static slice.
- deprecations: HashMap<SharedString, SharedString>,
+ by_name: HashMap<&'static str, ActionData>,
+ names_by_type_id: HashMap<TypeId, &'static str>,
+ 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<schemars::schema::Schema>,
+ 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<A: Action>(&mut self) {
self.insert_action(MacroActionData {
- name: A::debug_name(),
- aliases: A::deprecated_aliases(),
+ name: A::name_for_type(),
type_id: TypeId::of::<A>(),
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<schemars::schema::Schema>)> {
+ ) -> Vec<(&'static str, Option<schemars::schema::Schema>)> {
// 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::<Vec<_>>()
}
- pub fn action_deprecations(&self) -> &HashMap<SharedString, SharedString> {
- &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<MacroActionData> {
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<dyn gpui::Action>> {
- Ok(Box::new(Self))
- },
- fn action_json_schema(
- _: &mut gpui::private::schemars::r#gen::SchemaGenerator,
- ) -> Option<gpui::private::schemars::schema::Schema> {
- 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<dyn gpui::Action>> {
- Ok(Box::new(Self))
- },
- fn action_json_schema(
- generator: &mut gpui::private::schemars::r#gen::SchemaGenerator,
- ) -> Option<gpui::private::schemars::schema::Schema> {
- 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<dyn gpui::Action>> {
- Ok(Box::new(Self))
- },
-
- fn action_json_schema(
- generator: &mut gpui::private::schemars::r#gen::SchemaGenerator,
- ) -> Option<gpui::private::schemars::schema::Schema> {
- 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<dyn gpui::Action>> {
- Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::<Self>(value)?))
- },
-
- fn action_json_schema(
- generator: &mut gpui::private::schemars::r#gen::SchemaGenerator,
- ) -> Option<gpui::private::schemars::schema::Schema> {
- Some(<Self as gpui::private::schemars::JsonSchema>::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<dyn gpui::Action>> {
- Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::<Self>(value)?))
- },
- fn action_json_schema(
- generator: &mut gpui::private::schemars::r#gen::SchemaGenerator,
- ) -> Option<gpui::private::schemars::schema::Schema> {
- Some(<Self as gpui::private::schemars::JsonSchema>::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<dyn gpui::Action>> {
- 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<gpui::private::schemars::schema::Schema> {
- 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<dyn gpui::Action>> {
- Ok(std::boxed::Box::new(
- gpui::private::serde_json::from_value::<Self>(value)?,
- ))
- },
- fn action_json_schema(
- generator: &mut gpui::private::schemars::r#gen::SchemaGenerator,
- ) -> Option<gpui::private::schemars::schema::Schema> {
- Some(<Self as gpui::private::schemars::JsonSchema>::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::<Self>()
- .map_or(false, |a| self == a)
- }
-
- fn boxed_clone(&self) -> std::boxed::Box<dyn gpui::Action> {
- ::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 {
@@ -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<schemars::schema::Schema>)> {
+ ) -> Vec<(&'static str, Option<schemars::schema::Schema>)> {
self.actions.action_schemas(generator)
}
- /// Get a list of all deprecated action aliases and their canonical names.
- pub fn action_deprecations(&self) -> &HashMap<SharedString, SharedString> {
- 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.
@@ -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<Self>) -> impl IntoElement {
@@ -634,7 +634,7 @@ mod tests {
"test::TestAction"
}
- fn debug_name() -> &'static str
+ fn name_for_type() -> &'static str
where
Self: ::std::marker::Sized,
{
@@ -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,]
);
@@ -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
]
@@ -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,
{
@@ -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::<Token![=]>()?;
+ 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::<Token![=]>()?;
+ 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::<Token![=]>()?;
+ // Parse array of string literals
+ let content;
+ syn::bracketed!(content in meta.input);
+ let aliases = content.parse_terminated(
+ |input: ParseStream| input.parse::<LitStr>(),
+ 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::<Token![=]>()?;
+ 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::<Self>(_value)?)) }
+ };
+
+ let json_schema_fn_body = if no_json || is_unit_struct {
+ quote! { None }
+ } else {
+ quote! { Some(<Self as gpui::private::schemars::JsonSchema>::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::<Self>()
+ .map_or(false, |a| self == a)
+ }
+
+ fn boxed_clone(&self) -> Box<dyn gpui::Action> {
+ Box::new(self.clone())
+ }
+
+ fn build(_value: gpui::private::serde_json::Value) -> gpui::Result<Box<dyn gpui::Action>> {
+ #build_fn_body
+ }
+
+ fn action_json_schema(
+ _generator: &mut gpui::private::schemars::r#gen::SchemaGenerator,
+ ) -> Option<gpui::private::schemars::schema::Schema> {
+ #json_schema_fn_body
+ }
+
+ fn deprecated_aliases() -> &'static [&'static str] {
+ #deprecated_aliases_fn_body
+ }
+
+ fn deprecation_message() -> Option<&'static str> {
+ #deprecation_fn_body
+ }
+ }
+ })
+}
@@ -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
@@ -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 {
}
}
}
-
-
}
}
@@ -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: Task<Result<()>>,
@@ -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<Arc<Path>>,
}
-#[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,
[
@@ -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 {
@@ -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<Schema>)>,
- deprecations: &HashMap<SharedString, SharedString>,
+ action_schemas: Vec<(&'static str, Option<Schema>)>,
+ deprecations: &HashMap<&'static str, &'static str>,
+ deprecation_messages: &HashMap<&'static str, &'static str>,
) -> serde_json::Value {
fn set<I, O>(input: I) -> Option<O>
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 {
@@ -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) {
@@ -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 {
@@ -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);
@@ -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"))]
@@ -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);
@@ -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,
@@ -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<VimOption>,
}
-#[derive(Debug)]
-struct WrappedAction(Box<dyn Action>);
-
-#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
+#[derive(Clone, PartialEq, Action)]
+#[action(namespace = vim, no_json, no_register)]
struct VimSave {
pub save_intent: Option<SaveIntent>,
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<dyn Action>);
impl PartialEq for WrappedAction {
fn eq(&self, other: &Self) -> bool {
@@ -1289,7 +1281,8 @@ fn generate_positions(string: &str, query: &str) -> Vec<usize> {
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<CommandRange>,
@@ -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>) {
Vim::action(editor, cx, Vim::literal)
@@ -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>) {
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>) {
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>) {
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,
@@ -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>) {
Vim::action(editor, cx, |vim, action: &Increment, window, cx| {
vim.record_current_action(cx);
@@ -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>) {
self.record_current_action(cx);
@@ -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>) {
Vim::action(editor, cx, Vim::move_to_next);
@@ -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,
[
@@ -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<char>,
}
-#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
+#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]
+#[action(namespace = vim)]
#[serde(deny_unknown_fields)]
struct PushSneakBackward {
first_char: Option<char>,
}
-#[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<Object>,
}
-#[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<char>,
}
-#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
+#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]
+#[action(namespace = vim)]
#[serde(deny_unknown_fields)]
struct PushLiteral {
prefix: Option<String>,
@@ -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);
@@ -955,7 +955,7 @@ pub mod test {
pub focus_handle: FocusHandle,
pub size: Pixels,
}
- actions!(test, [ToggleTestPanel]);
+ actions!(test_only, [ToggleTestPanel]);
impl EventEmitter<PanelEvent> for TestPanel {}
@@ -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<SaveIntent>,
@@ -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<SaveIntent>,
@@ -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<SaveIntent>,
@@ -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<u64>,
}
-#[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<String>,
}
-impl_actions!(
- pane,
- [
- CloseAllItems,
- CloseActiveItem,
- CloseCleanItems,
- CloseItemsToTheLeft,
- CloseItemsToTheRight,
- CloseInactiveItems,
- ActivateItem,
- RevealInProjectPanel,
- DeploySearch,
- ]
-);
-
actions!(
pane,
[
@@ -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<PathBuf>,
}
-#[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<SaveIntent>,
}
-#[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<SaveIntent>,
}
-#[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<SaveIntent>,
}
-#[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<SaveIntent>,
}
-#[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<PathBuf>,
}
-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,
@@ -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
@@ -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::<Vec<ActionDef>>();
@@ -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::<Vec<_>>()
+ .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::<Vec<_>>();
+ 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::<Vec<_>>()
+ );
+ });
+ }
+
#[gpui::test]
fn test_bundled_settings_and_themes(cx: &mut App) {
cx.text_system()
@@ -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<ExtensionCategoryFilter>,
}
-#[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<Vec<String>>,
}
-
- 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<Vec<String>>,
}
-
- 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<Uuid>,
}
- 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<String>,
}
-
- 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<String>,
}
-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<fn(AnyView, &mut Window, &mut App)> = OnceLock::new();
}