Improve keymap json schema (#23044)

Also:

* Adds `impl_internal_actions!` for deriving the `Action` trait without
registering.

* Removes some deserializers that immediately fail in favor of
`#[serde(skip)]` on fields where they were used. This also omits them
from the schema.

Release Notes:

- Keymap settings file now has more JSON schema information to inform
`json-language-server` completions and info, particularly for actions
that take input.
This commit is contained in:
Michael Sloan 2025-01-12 19:34:35 -07:00 committed by GitHub
parent 4c50201036
commit 6aba3950d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 506 additions and 283 deletions

6
Cargo.lock generated
View file

@ -6817,6 +6817,7 @@ dependencies = [
"regex", "regex",
"rope", "rope",
"rust-embed", "rust-embed",
"schemars",
"serde", "serde",
"serde_json", "serde_json",
"settings", "settings",
@ -9332,6 +9333,7 @@ dependencies = [
"env_logger 0.11.6", "env_logger 0.11.6",
"gpui", "gpui",
"menu", "menu",
"schemars",
"serde", "serde",
"serde_json", "serde_json",
"ui", "ui",
@ -11278,6 +11280,7 @@ dependencies = [
"language", "language",
"menu", "menu",
"project", "project",
"schemars",
"serde", "serde",
"serde_json", "serde_json",
"settings", "settings",
@ -12659,6 +12662,7 @@ dependencies = [
"menu", "menu",
"picker", "picker",
"project", "project",
"schemars",
"serde", "serde",
"serde_json", "serde_json",
"settings", "settings",
@ -12852,6 +12856,7 @@ dependencies = [
"language", "language",
"project", "project",
"rand 0.8.5", "rand 0.8.5",
"schemars",
"search", "search",
"serde", "serde",
"serde_json", "serde_json",
@ -13186,6 +13191,7 @@ dependencies = [
"project", "project",
"remote", "remote",
"rpc", "rpc",
"schemars",
"serde", "serde",
"settings", "settings",
"smallvec", "smallvec",

View file

@ -26,7 +26,7 @@ pub use context::*;
pub use context_store::*; pub use context_store::*;
use feature_flags::FeatureFlagAppExt; use feature_flags::FeatureFlagAppExt;
use fs::Fs; use fs::Fs;
use gpui::impl_actions; use gpui::impl_internal_actions;
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal}; use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal};
pub(crate) use inline_assistant::*; pub(crate) use inline_assistant::*;
use language_model::{ use language_model::{
@ -74,13 +74,13 @@ actions!(
] ]
); );
#[derive(PartialEq, Clone, Deserialize)] #[derive(PartialEq, Clone)]
pub enum InsertDraggedFiles { pub enum InsertDraggedFiles {
ProjectPaths(Vec<PathBuf>), ProjectPaths(Vec<PathBuf>),
ExternalFiles(Vec<PathBuf>), ExternalFiles(Vec<PathBuf>),
} }
impl_actions!(assistant, [InsertDraggedFiles]); impl_internal_actions!(assistant, [InsertDraggedFiles]);
const DEFAULT_CONTEXT_LINES: usize = 50; const DEFAULT_CONTEXT_LINES: usize = 50;

View file

@ -1,82 +1,84 @@
//! This module contains all actions supported by [`Editor`]. //! This module contains all actions supported by [`Editor`].
use super::*; use super::*;
use gpui::{action_aliases, action_as}; use gpui::{action_aliases, action_as};
use schemars::JsonSchema;
use util::serde::default_true; use util::serde::default_true;
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct SelectNext { pub struct SelectNext {
#[serde(default)] #[serde(default)]
pub replace_newest: bool, pub replace_newest: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct SelectPrevious { pub struct SelectPrevious {
#[serde(default)] #[serde(default)]
pub replace_newest: bool, pub replace_newest: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct MoveToBeginningOfLine { pub struct MoveToBeginningOfLine {
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub stop_at_soft_wraps: bool, pub stop_at_soft_wraps: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct SelectToBeginningOfLine { pub struct SelectToBeginningOfLine {
#[serde(default)] #[serde(default)]
pub(super) stop_at_soft_wraps: bool, pub(super) stop_at_soft_wraps: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct MovePageUp { pub struct MovePageUp {
#[serde(default)] #[serde(default)]
pub(super) center_cursor: bool, pub(super) center_cursor: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct MovePageDown { pub struct MovePageDown {
#[serde(default)] #[serde(default)]
pub(super) center_cursor: bool, pub(super) center_cursor: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct MoveToEndOfLine { pub struct MoveToEndOfLine {
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub stop_at_soft_wraps: bool, pub stop_at_soft_wraps: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct SelectToEndOfLine { pub struct SelectToEndOfLine {
#[serde(default)] #[serde(default)]
pub(super) stop_at_soft_wraps: bool, pub(super) stop_at_soft_wraps: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct ToggleCodeActions { pub struct ToggleCodeActions {
// Display row from which the action was deployed. // Display row from which the action was deployed.
#[serde(default)] #[serde(default)]
#[serde(skip)]
pub deployed_from_indicator: Option<DisplayRow>, pub deployed_from_indicator: Option<DisplayRow>,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct ConfirmCompletion { pub struct ConfirmCompletion {
#[serde(default)] #[serde(default)]
pub item_ix: Option<usize>, pub item_ix: Option<usize>,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct ComposeCompletion { pub struct ComposeCompletion {
#[serde(default)] #[serde(default)]
pub item_ix: Option<usize>, pub item_ix: Option<usize>,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct ConfirmCodeAction { pub struct ConfirmCodeAction {
#[serde(default)] #[serde(default)]
pub item_ix: Option<usize>, pub item_ix: Option<usize>,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct ToggleComments { pub struct ToggleComments {
#[serde(default)] #[serde(default)]
pub advance_downwards: bool, pub advance_downwards: bool,
@ -84,84 +86,87 @@ pub struct ToggleComments {
pub ignore_indent: bool, pub ignore_indent: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct FoldAt { pub struct FoldAt {
#[serde(skip)]
pub buffer_row: MultiBufferRow, pub buffer_row: MultiBufferRow,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct UnfoldAt { pub struct UnfoldAt {
#[serde(skip)]
pub buffer_row: MultiBufferRow, pub buffer_row: MultiBufferRow,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct MoveUpByLines { pub struct MoveUpByLines {
#[serde(default)] #[serde(default)]
pub(super) lines: u32, pub(super) lines: u32,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct MoveDownByLines { pub struct MoveDownByLines {
#[serde(default)] #[serde(default)]
pub(super) lines: u32, pub(super) lines: u32,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct SelectUpByLines { pub struct SelectUpByLines {
#[serde(default)] #[serde(default)]
pub(super) lines: u32, pub(super) lines: u32,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct SelectDownByLines { pub struct SelectDownByLines {
#[serde(default)] #[serde(default)]
pub(super) lines: u32, pub(super) lines: u32,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct ExpandExcerpts { pub struct ExpandExcerpts {
#[serde(default)] #[serde(default)]
pub(super) lines: u32, pub(super) lines: u32,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct ExpandExcerptsUp { pub struct ExpandExcerptsUp {
#[serde(default)] #[serde(default)]
pub(super) lines: u32, pub(super) lines: u32,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct ExpandExcerptsDown { pub struct ExpandExcerptsDown {
#[serde(default)] #[serde(default)]
pub(super) lines: u32, pub(super) lines: u32,
} }
#[derive(PartialEq, Clone, Deserialize, Default)]
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct ShowCompletions { pub struct ShowCompletions {
#[serde(default)] #[serde(default)]
pub(super) trigger: Option<String>, pub(super) trigger: Option<String>,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct HandleInput(pub String); pub struct HandleInput(pub String);
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct DeleteToNextWordEnd { pub struct DeleteToNextWordEnd {
#[serde(default)] #[serde(default)]
pub ignore_newlines: bool, pub ignore_newlines: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct DeleteToPreviousWordStart { pub struct DeleteToPreviousWordStart {
#[serde(default)] #[serde(default)]
pub ignore_newlines: bool, pub ignore_newlines: bool,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct FoldAtLevel { pub struct FoldAtLevel {
pub level: u32, pub level: u32,
} }
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct SpawnNearestTask { pub struct SpawnNearestTask {
#[serde(default)] #[serde(default)]
pub reveal: task::RevealStrategy, pub reveal: task::RevealStrategy,

View file

@ -63,6 +63,16 @@ pub trait Action: 'static + Send {
where where
Self: Sized; Self: Sized;
/// Optional JSON schema for the action's input data.
fn action_json_schema(
_: &mut schemars::gen::SchemaGenerator,
) -> Option<schemars::schema::Schema>
where
Self: Sized,
{
None
}
/// A list of alternate, deprecated names for this action. /// A list of alternate, deprecated names for this action.
fn deprecated_aliases() -> &'static [&'static str] fn deprecated_aliases() -> &'static [&'static str]
where where
@ -90,16 +100,16 @@ impl dyn Action {
type ActionBuilder = fn(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>; type ActionBuilder = fn(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>;
pub(crate) struct ActionRegistry { pub(crate) struct ActionRegistry {
builders_by_name: HashMap<SharedString, ActionBuilder>, by_name: HashMap<SharedString, ActionData>,
names_by_type_id: HashMap<TypeId, SharedString>, names_by_type_id: HashMap<TypeId, SharedString>,
all_names: Vec<SharedString>, // So we can return a static slice. all_names: Vec<SharedString>, // So we can return a static slice.
deprecations: Vec<(SharedString, SharedString)>, deprecations: HashMap<SharedString, SharedString>,
} }
impl Default for ActionRegistry { impl Default for ActionRegistry {
fn default() -> Self { fn default() -> Self {
let mut this = ActionRegistry { let mut this = ActionRegistry {
builders_by_name: Default::default(), by_name: Default::default(),
names_by_type_id: Default::default(), names_by_type_id: Default::default(),
all_names: Default::default(), all_names: Default::default(),
deprecations: Default::default(), deprecations: Default::default(),
@ -111,19 +121,25 @@ impl Default for ActionRegistry {
} }
} }
/// This type must be public so that our macros can build it in other crates. struct ActionData {
/// But this is an implementation detail and should not be used directly. pub build: ActionBuilder,
#[doc(hidden)] pub json_schema: fn(&mut schemars::gen::SchemaGenerator) -> Option<schemars::schema::Schema>,
pub type MacroActionBuilder = fn() -> ActionData; }
/// This type must be public so that our macros can build it in other crates. /// This type must be public so that our macros can build it in other crates.
/// But this is an implementation detail and should not be used directly. /// But this is an implementation detail and should not be used directly.
#[doc(hidden)] #[doc(hidden)]
pub struct ActionData { pub type MacroActionBuilder = fn() -> MacroActionData;
/// This type must be public so that our macros can build it in other crates.
/// But this is an implementation detail and should not be used directly.
#[doc(hidden)]
pub struct MacroActionData {
pub name: &'static str, pub name: &'static str,
pub aliases: &'static [&'static str], pub aliases: &'static [&'static str],
pub type_id: TypeId, pub type_id: TypeId,
pub build: ActionBuilder, pub build: ActionBuilder,
pub json_schema: fn(&mut schemars::gen::SchemaGenerator) -> Option<schemars::schema::Schema>,
} }
/// This constant must be public to be accessible from other crates. /// This constant must be public to be accessible from other crates.
@ -143,20 +159,35 @@ impl ActionRegistry {
#[cfg(test)] #[cfg(test)]
pub(crate) fn load_action<A: Action>(&mut self) { pub(crate) fn load_action<A: Action>(&mut self) {
self.insert_action(ActionData { self.insert_action(MacroActionData {
name: A::debug_name(), name: A::debug_name(),
aliases: A::deprecated_aliases(), aliases: A::deprecated_aliases(),
type_id: TypeId::of::<A>(), type_id: TypeId::of::<A>(),
build: A::build, build: A::build,
json_schema: A::action_json_schema,
}); });
} }
fn insert_action(&mut self, action: ActionData) { fn insert_action(&mut self, action: MacroActionData) {
let name: SharedString = action.name.into(); let name: SharedString = action.name.into();
self.builders_by_name.insert(name.clone(), action.build); self.by_name.insert(
name.clone(),
ActionData {
build: action.build,
json_schema: action.json_schema,
},
);
for &alias in action.aliases { for &alias in action.aliases {
self.builders_by_name.insert(alias.into(), action.build); let alias: SharedString = alias.into();
self.deprecations.push((alias.into(), name.clone())); self.by_name.insert(
alias.clone(),
ActionData {
build: action.build,
json_schema: action.json_schema,
},
);
self.deprecations.insert(alias.clone(), name.clone());
self.all_names.push(alias);
} }
self.names_by_type_id.insert(action.type_id, name.clone()); self.names_by_type_id.insert(action.type_id, name.clone());
self.all_names.push(name); self.all_names.push(name);
@ -180,9 +211,10 @@ impl ActionRegistry {
params: Option<serde_json::Value>, params: Option<serde_json::Value>,
) -> Result<Box<dyn Action>> { ) -> Result<Box<dyn Action>> {
let build_action = self let build_action = self
.builders_by_name .by_name
.get(name) .get(name)
.ok_or_else(|| anyhow!("no action type registered for {}", name))?; .ok_or_else(|| anyhow!("No action type registered for {}", name))?
.build;
(build_action)(params.unwrap_or_else(|| json!({}))) (build_action)(params.unwrap_or_else(|| json!({})))
.with_context(|| format!("Attempting to build action {}", name)) .with_context(|| format!("Attempting to build action {}", name))
} }
@ -191,12 +223,30 @@ impl ActionRegistry {
self.all_names.as_slice() self.all_names.as_slice()
} }
pub fn action_deprecations(&self) -> &[(SharedString, SharedString)] { pub fn action_schemas(
self.deprecations.as_slice() &self,
generator: &mut schemars::gen::SchemaGenerator,
) -> Vec<(SharedString, Option<schemars::schema::Schema>)> {
// Use the order from all_names so that the resulting schema has sensible order.
self.all_names
.iter()
.map(|name| {
let action_data = self
.by_name
.get(name)
.expect("All actions in all_names should be registered");
(name.clone(), (action_data.json_schema)(generator))
})
.collect::<Vec<_>>()
}
pub fn action_deprecations(&self) -> &HashMap<SharedString, SharedString> {
&self.deprecations
} }
} }
/// Defines unit structs that can be used as actions. /// Defines and registers unit structs that can be used as actions.
///
/// To use more complex data types as actions, use `impl_actions!` /// To use more complex data types as actions, use `impl_actions!`
#[macro_export] #[macro_export]
macro_rules! actions { macro_rules! actions {
@ -211,6 +261,11 @@ macro_rules! actions {
gpui::__impl_action!($namespace, $name, $name, gpui::__impl_action!($namespace, $name, $name,
fn build(_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> { fn build(_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(Box::new(Self)) Ok(Box::new(Self))
},
fn action_json_schema(
_: &mut gpui::private::schemars::gen::SchemaGenerator,
) -> Option<gpui::private::schemars::schema::Schema> {
None
} }
); );
@ -219,11 +274,10 @@ macro_rules! actions {
}; };
} }
/// Defines a unit struct that can be used as an actions, with a name /// Defines and registers a unit struct that can be used as an actions, with a name that differs
/// that differs from it's type name. /// from it's type name.
/// ///
/// To use more complex data types as actions, and rename them use /// To use more complex data types as actions, and rename them use `impl_action_as!`
/// `impl_action_as!`
#[macro_export] #[macro_export]
macro_rules! action_as { macro_rules! action_as {
($namespace:path, $name:ident as $visual_name:ident) => { ($namespace:path, $name:ident as $visual_name:ident) => {
@ -241,6 +295,11 @@ macro_rules! action_as {
_: gpui::private::serde_json::Value, _: gpui::private::serde_json::Value,
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> { ) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(Box::new(Self)) Ok(Box::new(Self))
},
fn action_json_schema(
generator: &mut gpui::private::schemars::gen::SchemaGenerator,
) -> Option<gpui::private::schemars::schema::Schema> {
None
} }
); );
@ -248,7 +307,7 @@ macro_rules! action_as {
}; };
} }
/// Defines a unit struct that can be used as an action, with some deprecated aliases. /// Defines and registers a unit struct that can be used as an action, with some deprecated aliases.
#[macro_export] #[macro_export]
macro_rules! action_aliases { macro_rules! action_aliases {
($namespace:path, $name:ident, [$($alias:ident),* $(,)?]) => { ($namespace:path, $name:ident, [$($alias:ident),* $(,)?]) => {
@ -261,6 +320,7 @@ macro_rules! action_aliases {
::std::default::Default, ::std::default::Default,
::std::fmt::Debug, ::std::fmt::Debug,
gpui::private::serde_derive::Deserialize, gpui::private::serde_derive::Deserialize,
gpui::private::schemars::JsonSchema,
)] )]
#[serde(crate = "gpui::private::serde")] #[serde(crate = "gpui::private::serde")]
pub struct $name; pub struct $name;
@ -274,6 +334,12 @@ macro_rules! action_aliases {
) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> { ) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> {
Ok(Box::new(Self)) Ok(Box::new(Self))
}, },
fn action_json_schema(
generator: &mut gpui::private::schemars::gen::SchemaGenerator,
) -> Option<gpui::private::schemars::schema::Schema> {
None
},
fn deprecated_aliases() -> &'static [&'static str] { fn deprecated_aliases() -> &'static [&'static str] {
&[ &[
$(concat!(stringify!($namespace), "::", stringify!($alias))),* $(concat!(stringify!($namespace), "::", stringify!($alias))),*
@ -285,7 +351,11 @@ macro_rules! action_aliases {
}; };
} }
/// Implements the Action trait for any struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize /// Registers the action and implements the Action trait for any struct that implements Clone,
/// Default, PartialEq, serde_deserialize::Deserialize, and schemars::JsonSchema.
///
/// Fields and variants that don't make sense for user configuration should be annotated with
/// #[serde(skip)].
#[macro_export] #[macro_export]
macro_rules! impl_actions { macro_rules! impl_actions {
($namespace:path, [ $($name:ident),* $(,)? ]) => { ($namespace:path, [ $($name:ident),* $(,)? ]) => {
@ -293,6 +363,13 @@ macro_rules! impl_actions {
gpui::__impl_action!($namespace, $name, $name, gpui::__impl_action!($namespace, $name, $name,
fn build(value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box<dyn gpui::Action>> { 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)?)) Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::<Self>(value)?))
},
fn action_json_schema(
generator: &mut gpui::private::schemars::gen::SchemaGenerator,
) -> Option<gpui::private::schemars::schema::Schema> {
Some(<Self as gpui::private::schemars::JsonSchema>::json_schema(
generator,
))
} }
); );
@ -301,8 +378,41 @@ macro_rules! impl_actions {
}; };
} }
/// Implements the Action trait for a struct that implements Clone, Default, PartialEq, and serde_deserialize::Deserialize /// Implements the Action trait for internal action structs that implement Clone, Default,
/// Allows you to rename the action visually, without changing the struct's name /// 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::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_export]
macro_rules! impl_action_as { macro_rules! impl_action_as {
($namespace:path, $name:ident as $visual_name:tt ) => { ($namespace:path, $name:ident as $visual_name:tt ) => {
@ -316,6 +426,13 @@ macro_rules! impl_action_as {
Ok(std::boxed::Box::new( Ok(std::boxed::Box::new(
gpui::private::serde_json::from_value::<Self>(value)?, gpui::private::serde_json::from_value::<Self>(value)?,
)) ))
},
fn action_json_schema(
generator: &mut gpui::private::schemars::gen::SchemaGenerator,
) -> Option<gpui::private::schemars::schema::Schema> {
Some(<Self as gpui::private::schemars::JsonSchema>::json_schema(
generator,
))
} }
); );

View file

@ -23,7 +23,7 @@ use parking_lot::RwLock;
use slotmap::SlotMap; use slotmap::SlotMap;
pub use async_context::*; pub use async_context::*;
use collections::{FxHashMap, FxHashSet, VecDeque}; use collections::{FxHashMap, FxHashSet, HashMap, VecDeque};
pub use entity_map::*; pub use entity_map::*;
use http_client::HttpClient; use http_client::HttpClient;
pub use model_context::*; pub use model_context::*;
@ -1218,16 +1218,22 @@ impl AppContext {
self.actions.build_action(name, data) self.actions.build_action(name, data)
} }
/// Get a list of all action names that have been registered. /// Get all action names that have been registered. Note that registration only allows for
/// in the application. Note that registration only allows for /// actions to be built dynamically, and is unrelated to binding actions in the element tree.
/// 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) -> &[SharedString] {
self.actions.all_action_names() self.actions.all_action_names()
} }
/// Get all non-internal actions that have been registered, along with their schemas.
pub fn action_schemas(
&self,
generator: &mut schemars::gen::SchemaGenerator,
) -> Vec<(SharedString, Option<schemars::schema::Schema>)> {
self.actions.action_schemas(generator)
}
/// Get a list of all deprecated action aliases and their canonical names. /// Get a list of all deprecated action aliases and their canonical names.
pub fn action_deprecations(&self) -> &[(SharedString, SharedString)] { pub fn action_deprecations(&self) -> &HashMap<SharedString, SharedString> {
self.actions.action_deprecations() self.actions.action_deprecations()
} }

View file

@ -102,7 +102,9 @@ mod window;
/// Do not touch, here be dragons for use by gpui_macros and such. /// Do not touch, here be dragons for use by gpui_macros and such.
#[doc(hidden)] #[doc(hidden)]
pub mod private { pub mod private {
pub use anyhow;
pub use linkme; pub use linkme;
pub use schemars;
pub use serde; pub use serde;
pub use serde_derive; pub use serde_derive;
pub use serde_json; pub use serde_json;

View file

@ -1,12 +1,13 @@
use gpui::{actions, impl_actions}; use gpui::{actions, impl_actions};
use gpui_macros::register_action; use gpui_macros::register_action;
use schemars::JsonSchema;
use serde_derive::Deserialize; use serde_derive::Deserialize;
#[test] #[test]
fn test_action_macros() { fn test_action_macros() {
actions!(test, [TestAction]); actions!(test, [TestAction]);
#[derive(PartialEq, Clone, Deserialize)] #[derive(PartialEq, Clone, Deserialize, JsonSchema)]
struct AnotherTestAction; struct AnotherTestAction;
impl_actions!(test, [AnotherTestAction]); impl_actions!(test, [AnotherTestAction]);

View file

@ -29,12 +29,13 @@ pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream {
fn __autogenerated() { fn __autogenerated() {
/// This is an auto generated function, do not use. /// This is an auto generated function, do not use.
#[doc(hidden)] #[doc(hidden)]
fn #action_builder_fn_name() -> gpui::ActionData { fn #action_builder_fn_name() -> gpui::MacroActionData {
gpui::ActionData { gpui::MacroActionData {
name: <#type_name as gpui::Action>::debug_name(), name: <#type_name as gpui::Action>::debug_name(),
aliases: <#type_name as gpui::Action>::deprecated_aliases(), aliases: <#type_name as gpui::Action>::deprecated_aliases(),
type_id: ::std::any::TypeId::of::<#type_name>(), type_id: ::std::any::TypeId::of::<#type_name>(),
build: <#type_name as gpui::Action>::build, build: <#type_name as gpui::Action>::build,
json_schema: <#type_name as gpui::Action>::action_json_schema,
} }
} }
#[doc(hidden)] #[doc(hidden)]

View file

@ -662,8 +662,7 @@ pub struct LanguageConfigOverride {
pub line_comments: Override<Vec<Arc<str>>>, pub line_comments: Override<Vec<Arc<str>>>,
#[serde(default)] #[serde(default)]
pub block_comment: Override<(Arc<str>, Arc<str>)>, pub block_comment: Override<(Arc<str>, Arc<str>)>,
#[serde(skip_deserializing)] #[serde(skip)]
#[schemars(skip)]
pub disabled_bracket_ixs: Vec<u16>, pub disabled_bracket_ixs: Vec<u16>,
#[serde(default)] #[serde(default)]
pub word_characters: Override<HashSet<char>>, pub word_characters: Override<HashSet<char>>,
@ -776,7 +775,7 @@ pub struct BracketPairConfig {
pub pairs: Vec<BracketPair>, pub pairs: Vec<BracketPair>,
/// A list of tree-sitter scopes for which a given bracket should not be active. /// A list of tree-sitter scopes for which a given bracket should not be active.
/// N-th entry in `[Self::disabled_scopes_by_bracket_ix]` contains a list of disabled scopes for an n-th entry in `[Self::pairs]` /// N-th entry in `[Self::disabled_scopes_by_bracket_ix]` contains a list of disabled scopes for an n-th entry in `[Self::pairs]`
#[schemars(skip)] #[serde(skip)]
pub disabled_scopes_by_bracket_ix: Vec<Vec<String>>, pub disabled_scopes_by_bracket_ix: Vec<Vec<String>>,
} }

View file

@ -56,6 +56,7 @@ project.workspace = true
regex.workspace = true regex.workspace = true
rope.workspace = true rope.workspace = true
rust-embed.workspace = true rust-embed.workspace = true
schemars.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
settings.workspace = true settings.workspace = true

View file

@ -10,6 +10,7 @@ use language::{LanguageRegistry, LanguageToolchainStore, LspAdapter, LspAdapterD
use lsp::{LanguageServerBinary, LanguageServerName}; use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use project::{lsp_store::language_server_settings, ContextProviderWithTasks}; use project::{lsp_store::language_server_settings, ContextProviderWithTasks};
use schemars::gen::SchemaSettings;
use serde_json::{json, Value}; use serde_json::{json, Value};
use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore}; use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
use smol::{ use smol::{
@ -75,9 +76,6 @@ impl JsonLspAdapter {
} }
fn get_workspace_config(language_names: Vec<String>, cx: &mut AppContext) -> Value { fn get_workspace_config(language_names: Vec<String>, cx: &mut AppContext) -> Value {
let action_names = cx.all_action_names();
let deprecations = cx.action_deprecations();
let font_names = &cx.text_system().all_font_names(); let font_names = &cx.text_system().all_font_names();
let settings_schema = cx.global::<SettingsStore>().json_schema( let settings_schema = cx.global::<SettingsStore>().json_schema(
&SettingsJsonSchemaParams { &SettingsJsonSchemaParams {
@ -117,7 +115,7 @@ impl JsonLspAdapter {
}, },
{ {
"fileMatch": [schema_file_match(paths::keymap_file())], "fileMatch": [schema_file_match(paths::keymap_file())],
"schema": KeymapFile::generate_json_schema(action_names, deprecations), "schema": Self::generate_keymap_schema(cx),
}, },
{ {
"fileMatch": [ "fileMatch": [
@ -131,6 +129,16 @@ impl JsonLspAdapter {
} }
}) })
} }
fn generate_keymap_schema(cx: &mut AppContext) -> Value {
let mut generator = SchemaSettings::draft07()
.with(|settings| settings.option_add_null_type = false)
.into_generator();
let action_schemas = cx.action_schemas(&mut generator);
let deprecations = cx.action_deprecations();
KeymapFile::generate_json_schema(generator, action_schemas, deprecations)
}
} }
#[async_trait(?Send)] #[async_trait(?Send)]

View file

@ -20,6 +20,7 @@ anyhow.workspace = true
editor.workspace = true editor.workspace = true
gpui.workspace = true gpui.workspace = true
menu.workspace = true menu.workspace = true
schemars.workspace = true
serde.workspace = true serde.workspace = true
ui.workspace = true ui.workspace = true
workspace.workspace = true workspace.workspace = true

View file

@ -7,6 +7,7 @@ use gpui::{
ViewContext, WindowContext, ViewContext, WindowContext,
}; };
use head::Head; use head::Head;
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use ui::{prelude::*, v_flex, Color, Divider, Label, ListItem, ListItemSpacing}; use ui::{prelude::*, v_flex, Color, Divider, Label, ListItem, ListItemSpacing};
@ -24,7 +25,7 @@ actions!(picker, [ConfirmCompletion]);
/// ConfirmInput is an alternative editor action which - instead of selecting active picker entry - treats pickers editor input literally, /// 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. /// performing some kind of action on it.
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(Clone, PartialEq, Deserialize, JsonSchema, Default)]
pub struct ConfirmInput { pub struct ConfirmInput {
pub secondary: bool, pub secondary: bool,
} }

View file

@ -1,10 +1,10 @@
mod project_panel_settings; mod project_panel_settings;
mod utils; mod utils;
use anyhow::{anyhow, Context as _, Result};
use client::{ErrorCode, ErrorExt}; use client::{ErrorCode, ErrorExt};
use language::DiagnosticSeverity; use collections::{hash_map, BTreeSet, HashMap};
use settings::{Settings, SettingsStore}; use command_palette_hooks::CommandPaletteFilter;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use editor::{ use editor::{
items::{ items::{
@ -15,10 +15,6 @@ use editor::{
Editor, EditorEvent, EditorSettings, ShowScrollbar, Editor, EditorEvent, EditorSettings, ShowScrollbar,
}; };
use file_icons::FileIcons; use file_icons::FileIcons;
use anyhow::{anyhow, Context as _, Result};
use collections::{hash_map, BTreeSet, HashMap};
use command_palette_hooks::CommandPaletteFilter;
use git::repository::GitFileStatus; use git::repository::GitFileStatus;
use gpui::{ use gpui::{
actions, anchored, deferred, div, impl_actions, point, px, size, uniform_list, Action, actions, anchored, deferred, div, impl_actions, point, px, size, uniform_list, Action,
@ -30,6 +26,7 @@ use gpui::{
VisualContext as _, WeakView, WindowContext, VisualContext as _, WeakView, WindowContext,
}; };
use indexmap::IndexMap; use indexmap::IndexMap;
use language::DiagnosticSeverity;
use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
use project::{ use project::{
relativize_path, Entry, EntryKind, Fs, Project, ProjectEntryId, ProjectPath, Worktree, relativize_path, Entry, EntryKind, Fs, Project, ProjectEntryId, ProjectPath, Worktree,
@ -38,7 +35,9 @@ use project::{
use project_panel_settings::{ use project_panel_settings::{
ProjectPanelDockPosition, ProjectPanelSettings, ShowDiagnostics, ShowIndentGuides, ProjectPanelDockPosition, ProjectPanelSettings, ShowDiagnostics, ShowIndentGuides,
}; };
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::any::TypeId; use std::any::TypeId;
use std::{ use std::{
@ -152,13 +151,13 @@ struct EntryDetails {
canonical_path: Option<Box<Path>>, canonical_path: Option<Box<Path>>,
} }
#[derive(PartialEq, Clone, Default, Debug, Deserialize)] #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
struct Delete { struct Delete {
#[serde(default)] #[serde(default)]
pub skip_prompt: bool, pub skip_prompt: bool,
} }
#[derive(PartialEq, Clone, Default, Debug, Deserialize)] #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
struct Trash { struct Trash {
#[serde(default)] #[serde(default)]
pub skip_prompt: bool, pub skip_prompt: bool,

View file

@ -31,6 +31,7 @@ gpui.workspace = true
language.workspace = true language.workspace = true
menu.workspace = true menu.workspace = true
project.workspace = true project.workspace = true
schemars.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
settings.workspace = true settings.workspace = true

View file

@ -22,6 +22,7 @@ use project::{
search::SearchQuery, search::SearchQuery,
search_history::{SearchHistory, SearchHistoryCursor}, search_history::{SearchHistory, SearchHistoryCursor},
}; };
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use settings::Settings; use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
@ -43,7 +44,7 @@ use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults};
const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50; const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
#[derive(PartialEq, Clone, Deserialize)] #[derive(PartialEq, Clone, Deserialize, JsonSchema)]
pub struct Deploy { pub struct Deploy {
#[serde(default = "util::serde::default_true")] #[serde(default = "util::serde::default_true")]
pub focus: bool, pub focus: bool,

View file

@ -1,11 +1,11 @@
use crate::{settings_store::parse_json_with_comments, SettingsAssets}; use crate::{settings_store::parse_json_with_comments, SettingsAssets};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use collections::BTreeMap; use collections::{BTreeMap, HashMap};
use gpui::{Action, AppContext, KeyBinding, SharedString}; use gpui::{Action, AppContext, KeyBinding, SharedString};
use schemars::{ use schemars::{
gen::{SchemaGenerator, SchemaSettings}, gen::SchemaGenerator,
schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation}, schema::{ArrayValidation, InstanceType, Metadata, Schema, SchemaObject, SubschemaValidation},
JsonSchema, Map, JsonSchema,
}; };
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
@ -140,55 +140,117 @@ impl KeymapFile {
} }
pub fn generate_json_schema( pub fn generate_json_schema(
action_names: &[SharedString], generator: SchemaGenerator,
deprecations: &[(SharedString, SharedString)], action_schemas: Vec<(SharedString, Option<Schema>)>,
deprecations: &HashMap<SharedString, SharedString>,
) -> serde_json::Value { ) -> serde_json::Value {
let mut root_schema = SchemaSettings::draft07() fn set<I, O>(input: I) -> Option<O>
.with(|settings| settings.option_add_null_type = false) where
.into_generator() I: Into<O>,
.into_root_schema_for::<KeymapFile>(); {
Some(input.into())
let mut alternatives = vec![
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
enum_values: Some(
action_names
.iter()
.map(|name| Value::String(name.to_string()))
.collect(),
),
..Default::default()
}),
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Array))),
..Default::default()
}),
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Null))),
..Default::default()
}),
];
for (old, new) in deprecations {
alternatives.push(Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
const_value: Some(Value::String(old.to_string())),
extensions: Map::from_iter([(
// deprecationMessage is not part of the JSON Schema spec,
// but json-language-server recognizes it.
"deprecationMessage".to_owned(),
format!("Deprecated, use {new}").into(),
)]),
..Default::default()
}));
} }
let action_schema = Schema::Object(SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
one_of: Some(alternatives),
..Default::default()
})),
..Default::default()
});
fn add_deprecation_notice(schema_object: &mut SchemaObject, new_name: &SharedString) {
schema_object.extensions.insert(
// deprecationMessage is not part of the JSON Schema spec,
// but json-language-server recognizes it.
"deprecationMessage".to_owned(),
format!("Deprecated, use {new_name}").into(),
);
}
let empty_object: SchemaObject = SchemaObject {
instance_type: set(InstanceType::Object),
..Default::default()
};
let mut keymap_action_alternatives = Vec::new();
for (name, action_schema) in action_schemas.iter() {
let schema = if let Some(Schema::Object(schema)) = action_schema {
Some(schema.clone())
} else {
None
};
// If the type has a description, also apply it to the value. Ideally it would be
// removed and applied to the overall array, but `json-language-server` does not show
// these descriptions.
let description = schema.as_ref().and_then(|schema| {
schema
.metadata
.as_ref()
.and_then(|metadata| metadata.description.as_ref())
});
let mut matches_action_name = SchemaObject {
const_value: Some(Value::String(name.to_string())),
..Default::default()
};
if let Some(description) = description {
matches_action_name.metadata = set(Metadata {
description: Some(description.clone()),
..Default::default()
});
}
// Add an alternative for plain action names.
let deprecation = deprecations.get(name);
let mut plain_action = SchemaObject {
instance_type: set(InstanceType::String),
const_value: Some(Value::String(name.to_string())),
..Default::default()
};
if let Some(new_name) = deprecation {
add_deprecation_notice(&mut plain_action, new_name);
}
keymap_action_alternatives.push(plain_action.into());
// When all fields are skipped or an empty struct is added with impl_actions! /
// impl_actions_as! an empty struct is produced. The action should be invoked without
// data in this case.
if let Some(schema) = schema {
if schema != empty_object {
let mut action_with_data = SchemaObject {
instance_type: set(InstanceType::Array),
array: Some(
ArrayValidation {
items: set(vec![matches_action_name.into(), schema.into()]),
min_items: Some(2),
max_items: Some(2),
..Default::default()
}
.into(),
),
..Default::default()
};
if let Some(new_name) = deprecation {
add_deprecation_notice(&mut action_with_data, new_name);
}
keymap_action_alternatives.push(action_with_data.into());
}
}
}
// Placing null first causes json-language-server to default assuming actions should be
// null, so place it last.
keymap_action_alternatives.push(
SchemaObject {
instance_type: set(InstanceType::Null),
..Default::default()
}
.into(),
);
let action_schema = SchemaObject {
subschemas: set(SubschemaValidation {
one_of: Some(keymap_action_alternatives),
..Default::default()
}),
..Default::default()
}
.into();
let mut root_schema = generator.into_root_schema_for::<KeymapFile>();
root_schema root_schema
.definitions .definitions
.insert("KeymapAction".to_owned(), action_schema); .insert("KeymapAction".to_owned(), action_schema);

View file

@ -19,6 +19,7 @@ gpui.workspace = true
menu.workspace = true menu.workspace = true
picker.workspace = true picker.workspace = true
project.workspace = true project.workspace = true
schemars.workspace = true
serde.workspace = true serde.workspace = true
settings.workspace = true settings.workspace = true
ui.workspace = true ui.workspace = true

View file

@ -10,6 +10,7 @@ use gpui::{
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::Project; use project::Project;
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use settings::Settings; use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
@ -23,7 +24,7 @@ use workspace::{
const PANEL_WIDTH_REMS: f32 = 28.; const PANEL_WIDTH_REMS: f32 = 28.;
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, JsonSchema, Default)]
pub struct Toggle { pub struct Toggle {
#[serde(default)] #[serde(default)]
pub select_last: bool, pub select_last: bool,

View file

@ -29,6 +29,7 @@ itertools.workspace = true
language.workspace = true language.workspace = true
project.workspace = true project.workspace = true
task.workspace = true task.workspace = true
schemars.workspace = true
search.workspace = true search.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true

View file

@ -15,6 +15,7 @@ use gpui::{
use language::Bias; use language::Bias;
use persistence::TERMINAL_DB; use persistence::TERMINAL_DB;
use project::{search::SearchQuery, terminals::TerminalKind, Fs, Metadata, Project}; use project::{search::SearchQuery, terminals::TerminalKind, Fs, Metadata, Project};
use schemars::JsonSchema;
use terminal::{ use terminal::{
alacritty_terminal::{ alacritty_terminal::{
index::Point, index::Point,
@ -66,14 +67,14 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
const GIT_DIFF_PATH_PREFIXES: &[char] = &['a', 'b']; const GIT_DIFF_PATH_PREFIXES: &[char] = &['a', 'b'];
///Event to transmit the scroll from the element to the view /// Event to transmit the scroll from the element to the view
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ScrollTerminal(pub i32); pub struct ScrollTerminal(pub i32);
#[derive(Clone, Debug, Default, Deserialize, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq)]
pub struct SendText(String); pub struct SendText(String);
#[derive(Clone, Debug, Default, Deserialize, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq)]
pub struct SendKeystroke(String); pub struct SendKeystroke(String);
impl_actions!(terminal, [SendText, SendKeystroke]); impl_actions!(terminal, [SendText, SendKeystroke]);

View file

@ -36,6 +36,7 @@ notifications.workspace = true
project.workspace = true project.workspace = true
remote.workspace = true remote.workspace = true
rpc.workspace = true rpc.workspace = true
schemars.workspace = true
serde.workspace = true serde.workspace = true
settings.workspace = true settings.workspace = true
smallvec.workspace = true smallvec.workspace = true

View file

@ -1,17 +1,18 @@
use gpui::{impl_actions, OwnedMenu, OwnedMenuItem, View}; use gpui::{impl_actions, OwnedMenu, OwnedMenuItem, View};
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use smallvec::SmallVec; use smallvec::SmallVec;
use ui::{prelude::*, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip}; use ui::{prelude::*, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip};
impl_actions!( impl_actions!(
app_menu, app_menu,
[OpenApplicationMenu, NavigateApplicationMenuInDirection,] [OpenApplicationMenu, NavigateApplicationMenuInDirection]
); );
#[derive(Clone, Deserialize, PartialEq, Default)] #[derive(Clone, Deserialize, JsonSchema, PartialEq, Default)]
pub struct OpenApplicationMenu(String); pub struct OpenApplicationMenu(String);
#[derive(Clone, Deserialize, PartialEq, Default)] #[derive(Clone, Deserialize, JsonSchema, PartialEq, Default)]
pub struct NavigateApplicationMenuInDirection(String); pub struct NavigateApplicationMenuInDirection(String);
#[derive(Clone)] #[derive(Clone)]

View file

@ -1,11 +1,3 @@
use std::{
iter::Peekable,
ops::{Deref, Range},
str::Chars,
sync::OnceLock,
time::Instant,
};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use command_palette_hooks::CommandInterceptResult; use command_palette_hooks::CommandInterceptResult;
use editor::{ use editor::{
@ -13,12 +5,22 @@ use editor::{
display_map::ToDisplayPoint, display_map::ToDisplayPoint,
Bias, Editor, ToPoint, Bias, Editor, ToPoint,
}; };
use gpui::{actions, impl_actions, Action, AppContext, Global, ViewContext, WindowContext}; use gpui::{
actions, impl_internal_actions, Action, AppContext, Global, ViewContext, WindowContext,
};
use language::Point; use language::Point;
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use regex::Regex; use regex::Regex;
use schemars::JsonSchema;
use search::{BufferSearchBar, SearchOptions}; use search::{BufferSearchBar, SearchOptions};
use serde::Deserialize; use serde::Deserialize;
use std::{
iter::Peekable,
ops::{Deref, Range},
str::Chars,
sync::OnceLock,
time::Instant,
};
use util::ResultExt; use util::ResultExt;
use workspace::{notifications::NotifyResultExt, SaveIntent}; use workspace::{notifications::NotifyResultExt, SaveIntent};
@ -33,24 +35,24 @@ use crate::{
Vim, Vim,
}; };
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Clone, Debug, PartialEq)]
pub struct GoToLine { pub struct GoToLine {
range: CommandRange, range: CommandRange,
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Clone, Debug, PartialEq)]
pub struct YankCommand { pub struct YankCommand {
range: CommandRange, range: CommandRange,
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Clone, Debug, PartialEq)]
pub struct WithRange { pub struct WithRange {
restore_selection: bool, restore_selection: bool,
range: CommandRange, range: CommandRange,
action: WrappedAction, action: WrappedAction,
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Clone, Debug, PartialEq)]
pub struct WithCount { pub struct WithCount {
count: u32, count: u32,
action: WrappedAction, action: WrappedAction,
@ -60,20 +62,11 @@ pub struct WithCount {
struct WrappedAction(Box<dyn Action>); struct WrappedAction(Box<dyn Action>);
actions!(vim, [VisualCommand, CountCommand]); actions!(vim, [VisualCommand, CountCommand]);
impl_actions!( impl_internal_actions!(
vim, vim,
[GoToLine, YankCommand, WithRange, WithCount, OnMatchingLines] [GoToLine, YankCommand, WithRange, WithCount, OnMatchingLines]
); );
impl<'de> Deserialize<'de> for WrappedAction {
fn deserialize<D>(_: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Err(serde::de::Error::custom("Cannot deserialize WrappedAction"))
}
}
impl PartialEq for WrappedAction { impl PartialEq for WrappedAction {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.0.partial_eq(&*other.0) self.0.partial_eq(&*other.0)
@ -423,7 +416,7 @@ impl VimCommand {
} }
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
enum Position { enum Position {
Line { row: u32, offset: i32 }, Line { row: u32, offset: i32 },
Mark { name: char, offset: i32 }, Mark { name: char, offset: i32 },
@ -467,7 +460,7 @@ impl Position {
} }
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Clone, Debug, PartialEq)]
pub(crate) struct CommandRange { pub(crate) struct CommandRange {
start: Position, start: Position,
end: Option<Position>, end: Option<Position>,
@ -877,7 +870,7 @@ fn generate_positions(string: &str, query: &str) -> Vec<usize> {
positions positions
} }
#[derive(Debug, PartialEq, Deserialize, Clone)] #[derive(Debug, PartialEq, Clone)]
pub(crate) struct OnMatchingLines { pub(crate) struct OnMatchingLines {
range: CommandRange, range: CommandRange,
search: String, search: String,

View file

@ -3,6 +3,7 @@ use std::sync::Arc;
use collections::HashMap; use collections::HashMap;
use editor::Editor; use editor::Editor;
use gpui::{impl_actions, AppContext, Keystroke, KeystrokeEvent}; use gpui::{impl_actions, AppContext, Keystroke, KeystrokeEvent};
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use settings::Settings; use settings::Settings;
use std::sync::LazyLock; use std::sync::LazyLock;
@ -12,7 +13,7 @@ use crate::{state::Operator, Vim, VimSettings};
mod default; mod default;
#[derive(PartialEq, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize, JsonSchema, PartialEq)]
struct Literal(String, char); struct Literal(String, char);
impl_actions!(vim, [Literal]); impl_actions!(vim, [Literal]);

View file

@ -9,6 +9,7 @@ use editor::{
use gpui::{actions, impl_actions, px, ViewContext}; use gpui::{actions, impl_actions, px, ViewContext};
use language::{CharKind, Point, Selection, SelectionGoal}; use language::{CharKind, Point, Selection, SelectionGoal};
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use std::ops::Range; use std::ops::Range;
use workspace::searchable::Direction; use workspace::searchable::Direction;
@ -139,105 +140,105 @@ pub enum Motion {
}, },
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct NextWordStart { struct NextWordStart {
#[serde(default)] #[serde(default)]
ignore_punctuation: bool, ignore_punctuation: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct NextWordEnd { struct NextWordEnd {
#[serde(default)] #[serde(default)]
ignore_punctuation: bool, ignore_punctuation: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct PreviousWordStart { struct PreviousWordStart {
#[serde(default)] #[serde(default)]
ignore_punctuation: bool, ignore_punctuation: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct PreviousWordEnd { struct PreviousWordEnd {
#[serde(default)] #[serde(default)]
ignore_punctuation: bool, ignore_punctuation: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub(crate) struct NextSubwordStart { pub(crate) struct NextSubwordStart {
#[serde(default)] #[serde(default)]
pub(crate) ignore_punctuation: bool, pub(crate) ignore_punctuation: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub(crate) struct NextSubwordEnd { pub(crate) struct NextSubwordEnd {
#[serde(default)] #[serde(default)]
pub(crate) ignore_punctuation: bool, pub(crate) ignore_punctuation: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub(crate) struct PreviousSubwordStart { pub(crate) struct PreviousSubwordStart {
#[serde(default)] #[serde(default)]
pub(crate) ignore_punctuation: bool, pub(crate) ignore_punctuation: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub(crate) struct PreviousSubwordEnd { pub(crate) struct PreviousSubwordEnd {
#[serde(default)] #[serde(default)]
pub(crate) ignore_punctuation: bool, pub(crate) ignore_punctuation: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub(crate) struct Up { pub(crate) struct Up {
#[serde(default)] #[serde(default)]
pub(crate) display_lines: bool, pub(crate) display_lines: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub(crate) struct Down { pub(crate) struct Down {
#[serde(default)] #[serde(default)]
pub(crate) display_lines: bool, pub(crate) display_lines: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct FirstNonWhitespace { struct FirstNonWhitespace {
#[serde(default)] #[serde(default)]
display_lines: bool, display_lines: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct EndOfLine { struct EndOfLine {
#[serde(default)] #[serde(default)]
display_lines: bool, display_lines: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct StartOfLine { pub struct StartOfLine {
#[serde(default)] #[serde(default)]
pub(crate) display_lines: bool, pub(crate) display_lines: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct UnmatchedForward { struct UnmatchedForward {
#[serde(default)] #[serde(default)]
char: char, char: char,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct UnmatchedBackward { struct UnmatchedBackward {
#[serde(default)] #[serde(default)]

View file

@ -1,20 +1,20 @@
use std::ops::Range;
use editor::{scroll::Autoscroll, Editor, MultiBufferSnapshot, ToOffset, ToPoint}; use editor::{scroll::Autoscroll, Editor, MultiBufferSnapshot, ToOffset, ToPoint};
use gpui::{impl_actions, ViewContext}; use gpui::{impl_actions, ViewContext};
use language::{Bias, Point}; use language::{Bias, Point};
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use std::ops::Range;
use crate::{state::Mode, Vim}; use crate::{state::Mode, Vim};
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct Increment { struct Increment {
#[serde(default)] #[serde(default)]
step: bool, step: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct Decrement { struct Decrement {
#[serde(default)] #[serde(default)]

View file

@ -1,16 +1,16 @@
use std::cmp;
use editor::{display_map::ToDisplayPoint, movement, scroll::Autoscroll, DisplayPoint, RowExt}; use editor::{display_map::ToDisplayPoint, movement, scroll::Autoscroll, DisplayPoint, RowExt};
use gpui::{impl_actions, ViewContext}; use gpui::{impl_actions, ViewContext};
use language::{Bias, SelectionGoal}; use language::{Bias, SelectionGoal};
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use std::cmp;
use crate::{ use crate::{
state::{Mode, Register}, state::{Mode, Register},
Vim, Vim,
}; };
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Paste { pub struct Paste {
#[serde(default)] #[serde(default)]

View file

@ -1,10 +1,10 @@
use std::{iter::Peekable, str::Chars, time::Duration};
use editor::Editor; use editor::Editor;
use gpui::{actions, impl_actions, ViewContext}; use gpui::{actions, impl_actions, impl_internal_actions, ViewContext};
use language::Point; use language::Point;
use schemars::JsonSchema;
use search::{buffer_search, BufferSearchBar, SearchOptions}; use search::{buffer_search, BufferSearchBar, SearchOptions};
use serde_derive::Deserialize; use serde_derive::Deserialize;
use std::{iter::Peekable, str::Chars, time::Duration};
use util::serde::default_true; use util::serde::default_true;
use workspace::{notifications::NotifyResultExt, searchable::Direction}; use workspace::{notifications::NotifyResultExt, searchable::Direction};
@ -15,7 +15,7 @@ use crate::{
Vim, Vim,
}; };
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub(crate) struct MoveToNext { pub(crate) struct MoveToNext {
#[serde(default = "default_true")] #[serde(default = "default_true")]
@ -26,7 +26,7 @@ pub(crate) struct MoveToNext {
regex: bool, regex: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub(crate) struct MoveToPrev { pub(crate) struct MoveToPrev {
#[serde(default = "default_true")] #[serde(default = "default_true")]
@ -37,7 +37,7 @@ pub(crate) struct MoveToPrev {
regex: bool, regex: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
pub(crate) struct Search { pub(crate) struct Search {
#[serde(default)] #[serde(default)]
backwards: bool, backwards: bool,
@ -45,19 +45,19 @@ pub(crate) struct Search {
regex: bool, regex: bool,
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
pub struct FindCommand { pub struct FindCommand {
pub query: String, pub query: String,
pub backwards: bool, pub backwards: bool,
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Clone, Debug, PartialEq)]
pub struct ReplaceCommand { pub struct ReplaceCommand {
pub(crate) range: CommandRange, pub(crate) range: CommandRange,
pub(crate) replacement: Replacement, pub(crate) replacement: Replacement,
} }
#[derive(Debug, Default, PartialEq, Deserialize, Clone)] #[derive(Clone, Debug, PartialEq)]
pub(crate) struct Replacement { pub(crate) struct Replacement {
search: String, search: String,
replacement: String, replacement: String,
@ -66,10 +66,8 @@ pub(crate) struct Replacement {
} }
actions!(vim, [SearchSubmit, MoveToNextMatch, MoveToPrevMatch]); actions!(vim, [SearchSubmit, MoveToNextMatch, MoveToPrevMatch]);
impl_actions!( impl_actions!(vim, [FindCommand, Search, MoveToPrev, MoveToNext]);
vim, impl_internal_actions!(vim, [ReplaceCommand]);
[FindCommand, ReplaceCommand, Search, MoveToPrev, MoveToNext]
);
pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) { pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
Vim::action(editor, cx, Vim::move_to_next); Vim::action(editor, cx, Vim::move_to_next);

View file

@ -10,15 +10,14 @@ use editor::{
movement::{self, FindRange}, movement::{self, FindRange},
Bias, DisplayPoint, Editor, Bias, DisplayPoint, Editor,
}; };
use itertools::Itertools;
use gpui::{actions, impl_actions, ViewContext}; use gpui::{actions, impl_actions, ViewContext};
use itertools::Itertools;
use language::{BufferSnapshot, CharKind, Point, Selection, TextObject, TreeSitterOptions}; use language::{BufferSnapshot, CharKind, Point, Selection, TextObject, TreeSitterOptions};
use multi_buffer::MultiBufferRow; use multi_buffer::MultiBufferRow;
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, JsonSchema)]
pub enum Object { pub enum Object {
Word { ignore_punctuation: bool }, Word { ignore_punctuation: bool },
Sentence, Sentence,
@ -40,13 +39,14 @@ pub enum Object {
Comment, Comment,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct Word { struct Word {
#[serde(default)] #[serde(default)]
ignore_punctuation: bool, ignore_punctuation: bool,
} }
#[derive(Clone, Deserialize, PartialEq)]
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct IndentObj { struct IndentObj {
#[serde(default)] #[serde(default)]

View file

@ -1,6 +1,3 @@
use std::borrow::BorrowMut;
use std::{fmt::Display, ops::Range, sync::Arc};
use crate::command::command_interceptor; use crate::command::command_interceptor;
use crate::normal::repeat::Replayer; use crate::normal::repeat::Replayer;
use crate::surrounds::SurroundsType; use crate::surrounds::SurroundsType;
@ -13,12 +10,15 @@ use gpui::{
Action, AppContext, BorrowAppContext, ClipboardEntry, ClipboardItem, Global, View, WeakView, Action, AppContext, BorrowAppContext, ClipboardEntry, ClipboardItem, Global, View, WeakView,
}; };
use language::Point; use language::Point;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsStore};
use std::borrow::BorrowMut;
use std::{fmt::Display, ops::Range, sync::Arc};
use ui::{SharedString, ViewContext}; use ui::{SharedString, ViewContext};
use workspace::searchable::Direction; use workspace::searchable::Direction;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, JsonSchema, Serialize)]
pub enum Mode { pub enum Mode {
Normal, Normal,
Insert, Insert,
@ -59,22 +59,39 @@ impl Default for Mode {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Deserialize, JsonSchema)]
pub enum Operator { pub enum Operator {
Change, Change,
Delete, Delete,
Yank, Yank,
Replace, Replace,
Object { around: bool }, Object {
FindForward { before: bool }, around: bool,
FindBackward { after: bool }, },
Sneak { first_char: Option<char> }, FindForward {
SneakBackward { first_char: Option<char> }, before: bool,
AddSurrounds { target: Option<SurroundsType> }, },
ChangeSurrounds { target: Option<Object> }, FindBackward {
after: bool,
},
Sneak {
first_char: Option<char>,
},
SneakBackward {
first_char: Option<char>,
},
AddSurrounds {
#[serde(skip)]
target: Option<SurroundsType>,
},
ChangeSurrounds {
target: Option<Object>,
},
DeleteSurrounds, DeleteSurrounds,
Mark, Mark,
Jump { line: bool }, Jump {
line: bool,
},
Indent, Indent,
Outdent, Outdent,
AutoIndent, AutoIndent,
@ -82,8 +99,12 @@ pub enum Operator {
Lowercase, Lowercase,
Uppercase, Uppercase,
OppositeCase, OppositeCase,
Digraph { first_char: Option<char> }, Digraph {
Literal { prefix: Option<String> }, first_char: Option<char>,
},
Literal {
prefix: Option<String>,
},
Register, Register,
RecordRegister, RecordRegister,
ReplayRegister, ReplayRegister,

View file

@ -6,7 +6,7 @@ use crate::{
}; };
use editor::{movement, scroll::Autoscroll, Bias}; use editor::{movement, scroll::Autoscroll, Bias};
use language::BracketPair; use language::BracketPair;
use serde::Deserialize;
use std::sync::Arc; use std::sync::Arc;
use ui::ViewContext; use ui::ViewContext;
@ -17,16 +17,6 @@ pub enum SurroundsType {
Selection, Selection,
} }
// This exists so that we can have Deserialize on Operators, but not on Motions.
impl<'de> Deserialize<'de> for SurroundsType {
fn deserialize<D>(_: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Err(serde::de::Error::custom("Cannot deserialize SurroundsType"))
}
}
impl Vim { impl Vim {
pub fn add_surrounds( pub fn add_surrounds(
&mut self, &mut self,

View file

@ -49,25 +49,25 @@ use workspace::{self, Pane, ResizeIntent, Workspace};
use crate::state::ReplayableAction; use crate::state::ReplayableAction;
/// Used to resize the current pane /// Used to resize the current pane
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
pub struct ResizePane(pub ResizeIntent); pub struct ResizePane(pub ResizeIntent);
/// An Action to Switch between modes /// An Action to Switch between modes
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
pub struct SwitchMode(pub Mode); pub struct SwitchMode(pub Mode);
/// PushOperator is used to put vim into a "minor" mode, /// PushOperator is used to put vim into a "minor" mode,
/// where it's waiting for a specific next set of keystrokes. /// where it's waiting for a specific next set of keystrokes.
/// For example 'd' needs a motion to complete. /// For example 'd' needs a motion to complete.
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
pub struct PushOperator(pub Operator); pub struct PushOperator(pub Operator);
/// Number is used to manage vim's count. Pushing a digit /// Number is used to manage vim's count. Pushing a digit
/// multiplis the current value by 10 and adds the digit. /// multiplies the current value by 10 and adds the digit.
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
struct Number(usize); struct Number(usize);
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, JsonSchema, PartialEq)]
struct SelectRegister(String); struct SelectRegister(String);
actions!( actions!(

View file

@ -25,6 +25,7 @@ use itertools::Itertools;
use language::DiagnosticSeverity; use language::DiagnosticSeverity;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::{Project, ProjectEntryId, ProjectPath, WorktreeId}; use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsStore};
use std::{ use std::{
@ -71,7 +72,7 @@ impl DraggedSelection {
} }
} }
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)] #[derive(Clone, Copy, PartialEq, Debug, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum SaveIntent { pub enum SaveIntent {
/// write all files (even if unchanged) /// write all files (even if unchanged)
@ -92,16 +93,16 @@ pub enum SaveIntent {
Skip, Skip,
} }
#[derive(Clone, Deserialize, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
pub struct ActivateItem(pub usize); pub struct ActivateItem(pub usize);
#[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CloseActiveItem { pub struct CloseActiveItem {
pub save_intent: Option<SaveIntent>, pub save_intent: Option<SaveIntent>,
} }
#[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CloseInactiveItems { pub struct CloseInactiveItems {
pub save_intent: Option<SaveIntent>, pub save_intent: Option<SaveIntent>,
@ -109,7 +110,7 @@ pub struct CloseInactiveItems {
pub close_pinned: bool, pub close_pinned: bool,
} }
#[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CloseAllItems { pub struct CloseAllItems {
pub save_intent: Option<SaveIntent>, pub save_intent: Option<SaveIntent>,
@ -117,34 +118,35 @@ pub struct CloseAllItems {
pub close_pinned: bool, pub close_pinned: bool,
} }
#[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CloseCleanItems { pub struct CloseCleanItems {
#[serde(default)] #[serde(default)]
pub close_pinned: bool, pub close_pinned: bool,
} }
#[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CloseItemsToTheRight { pub struct CloseItemsToTheRight {
#[serde(default)] #[serde(default)]
pub close_pinned: bool, pub close_pinned: bool,
} }
#[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CloseItemsToTheLeft { pub struct CloseItemsToTheLeft {
#[serde(default)] #[serde(default)]
pub close_pinned: bool, pub close_pinned: bool,
} }
#[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RevealInProjectPanel { pub struct RevealInProjectPanel {
#[serde(skip)]
pub entry_id: Option<u64>, pub entry_id: Option<u64>,
} }
#[derive(Default, PartialEq, Clone, Deserialize)] #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)]
pub struct DeploySearch { pub struct DeploySearch {
#[serde(default)] #[serde(default)]
pub replace_enabled: bool, pub replace_enabled: bool,

View file

@ -13,6 +13,7 @@ use gpui::{
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use project::Project; use project::Project;
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use settings::Settings; use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
@ -717,7 +718,7 @@ impl PaneAxis {
} }
} }
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] #[derive(Clone, Copy, Debug, Deserialize, PartialEq, JsonSchema)]
pub enum SplitDirection { pub enum SplitDirection {
Up, Up,
Down, Down,
@ -800,7 +801,7 @@ impl SplitDirection {
} }
} }
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] #[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq)]
pub enum ResizeIntent { pub enum ResizeIntent {
Lengthen, Lengthen,
Shorten, Shorten,

View file

@ -61,10 +61,9 @@ use persistence::{
SerializedWindowBounds, DB, SerializedWindowBounds, DB,
}; };
use postage::stream::Stream; use postage::stream::Stream;
use project::{ use project::{DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree};
DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId,
};
use remote::{ssh_session::ConnectionIdentifier, SshClientDelegate, SshConnectionOptions}; use remote::{ssh_session::ConnectionIdentifier, SshClientDelegate, SshConnectionOptions};
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use session::AppSession; use session::AppSession;
use settings::Settings; use settings::Settings;
@ -119,9 +118,6 @@ static ZED_WINDOW_POSITION: LazyLock<Option<Point<Pixels>>> = LazyLock::new(|| {
.and_then(parse_pixel_position_env_var) .and_then(parse_pixel_position_env_var)
}); });
#[derive(Clone, PartialEq)]
pub struct RemoveWorktreeFromProject(pub WorktreeId);
actions!(assistant, [ShowConfiguration]); actions!(assistant, [ShowConfiguration]);
actions!( actions!(
@ -165,64 +161,64 @@ pub struct OpenPaths {
pub paths: Vec<PathBuf>, pub paths: Vec<PathBuf>,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, PartialEq, JsonSchema)]
pub struct ActivatePane(pub usize); pub struct ActivatePane(pub usize);
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, PartialEq, JsonSchema)]
pub struct ActivatePaneInDirection(pub SplitDirection); pub struct ActivatePaneInDirection(pub SplitDirection);
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, PartialEq, JsonSchema)]
pub struct SwapPaneInDirection(pub SplitDirection); pub struct SwapPaneInDirection(pub SplitDirection);
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, PartialEq, JsonSchema)]
pub struct MoveItemToPane { pub struct MoveItemToPane {
pub destination: usize, pub destination: usize,
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub focus: bool, pub focus: bool,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, PartialEq, JsonSchema)]
pub struct MoveItemToPaneInDirection { pub struct MoveItemToPaneInDirection {
pub direction: SplitDirection, pub direction: SplitDirection,
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub focus: bool, pub focus: bool,
} }
#[derive(Clone, PartialEq, Debug, Deserialize)] #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct SaveAll { pub struct SaveAll {
pub save_intent: Option<SaveIntent>, pub save_intent: Option<SaveIntent>,
} }
#[derive(Clone, PartialEq, Debug, Deserialize)] #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Save { pub struct Save {
pub save_intent: Option<SaveIntent>, pub save_intent: Option<SaveIntent>,
} }
#[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CloseAllItemsAndPanes { pub struct CloseAllItemsAndPanes {
pub save_intent: Option<SaveIntent>, pub save_intent: Option<SaveIntent>,
} }
#[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CloseInactiveTabsAndPanes { pub struct CloseInactiveTabsAndPanes {
pub save_intent: Option<SaveIntent>, pub save_intent: Option<SaveIntent>,
} }
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, PartialEq, JsonSchema)]
pub struct SendKeystrokes(pub String); pub struct SendKeystrokes(pub String);
#[derive(Clone, Deserialize, PartialEq, Default)] #[derive(Clone, Deserialize, PartialEq, Default, JsonSchema)]
pub struct Reload { pub struct Reload {
pub binary_path: Option<PathBuf>, pub binary_path: Option<PathBuf>,
} }
action_as!(project_symbols, ToggleProjectSymbols as Toggle); action_as!(project_symbols, ToggleProjectSymbols as Toggle);
#[derive(Default, PartialEq, Eq, Clone, serde::Deserialize)] #[derive(Default, PartialEq, Eq, Clone, Deserialize, JsonSchema)]
pub struct ToggleFileFinder { pub struct ToggleFileFinder {
#[serde(default)] #[serde(default)]
pub separate_history: bool, pub separate_history: bool,
@ -299,7 +295,7 @@ impl PartialEq for Toast {
} }
} }
#[derive(Debug, Default, Clone, Deserialize, PartialEq)] #[derive(Debug, Default, Clone, Deserialize, PartialEq, JsonSchema)]
pub struct OpenTerminal { pub struct OpenTerminal {
pub working_directory: PathBuf, pub working_directory: PathBuf,
} }

View file

@ -11,12 +11,12 @@ use serde::{Deserialize, Serialize};
// https://github.com/mmastrac/rust-ctor/issues/280 // https://github.com/mmastrac/rust-ctor/issues/280
pub fn init() {} pub fn init() {}
#[derive(Clone, PartialEq, Deserialize)] #[derive(Clone, PartialEq, Deserialize, JsonSchema)]
pub struct OpenBrowser { pub struct OpenBrowser {
pub url: String, pub url: String,
} }
#[derive(Clone, PartialEq, Deserialize)] #[derive(Clone, PartialEq, Deserialize, JsonSchema)]
pub struct OpenZedUrl { pub struct OpenZedUrl {
pub url: String, pub url: String,
} }
@ -65,9 +65,10 @@ pub mod feedback {
pub mod theme_selector { pub mod theme_selector {
use gpui::impl_actions; use gpui::impl_actions;
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
#[derive(PartialEq, Clone, Default, Debug, Deserialize)] #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
pub struct Toggle { pub struct Toggle {
/// A list of theme names to filter the theme selector down to. /// A list of theme names to filter the theme selector down to.
pub themes_filter: Option<Vec<String>>, pub themes_filter: Option<Vec<String>>,
@ -76,20 +77,21 @@ pub mod theme_selector {
impl_actions!(theme_selector, [Toggle]); impl_actions!(theme_selector, [Toggle]);
} }
#[derive(Clone, Default, Deserialize, PartialEq)] #[derive(Clone, Default, Deserialize, PartialEq, JsonSchema)]
pub struct InlineAssist { pub struct InlineAssist {
pub prompt: Option<String>, pub prompt: Option<String>,
} }
impl_actions!(assistant, [InlineAssist]); impl_actions!(assistant, [InlineAssist]);
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct OpenRecent { pub struct OpenRecent {
#[serde(default)] #[serde(default)]
pub create_new_window: bool, pub create_new_window: bool,
} }
gpui::impl_actions!(projects, [OpenRecent]);
gpui::actions!(projects, [OpenRemote]); impl_actions!(projects, [OpenRecent]);
actions!(projects, [OpenRemote]);
/// Where to spawn the task in the UI. /// Where to spawn the task in the UI.
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
@ -102,8 +104,8 @@ pub enum RevealTarget {
Dock, Dock,
} }
/// Spawn a task with name or open tasks modal /// Spawn a task with name or open tasks modal.
#[derive(Debug, PartialEq, Clone, Deserialize)] #[derive(Debug, PartialEq, Clone, Deserialize, JsonSchema)]
#[serde(untagged)] #[serde(untagged)]
pub enum Spawn { pub enum Spawn {
/// Spawns a task by the name given. /// Spawns a task by the name given.
@ -128,8 +130,8 @@ impl Spawn {
} }
} }
/// Rerun last task /// Rerun the last task.
#[derive(PartialEq, Clone, Deserialize, Default)] #[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]
pub struct Rerun { pub struct Rerun {
/// Controls whether the task context is reevaluated prior to execution of a task. /// Controls whether the task context is reevaluated prior to execution of a task.
/// If it is not, environment variables such as ZED_COLUMN, ZED_FILE are gonna be the same as in the last execution of a task /// If it is not, environment variables such as ZED_COLUMN, ZED_FILE are gonna be the same as in the last execution of a task
@ -147,6 +149,7 @@ pub struct Rerun {
pub use_new_terminal: Option<bool>, pub use_new_terminal: Option<bool>,
/// If present, rerun the task with this ID, otherwise rerun the last task. /// If present, rerun the task with this ID, otherwise rerun the last task.
#[serde(skip)]
pub task_id: Option<String>, pub task_id: Option<String>,
} }