diff --git a/Cargo.lock b/Cargo.lock index 7f63e6aceb73c7..fd93fbfeb350f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6817,6 +6817,7 @@ dependencies = [ "regex", "rope", "rust-embed", + "schemars", "serde", "serde_json", "settings", @@ -9332,6 +9333,7 @@ dependencies = [ "env_logger 0.11.6", "gpui", "menu", + "schemars", "serde", "serde_json", "ui", @@ -11278,6 +11280,7 @@ dependencies = [ "language", "menu", "project", + "schemars", "serde", "serde_json", "settings", @@ -12659,6 +12662,7 @@ dependencies = [ "menu", "picker", "project", + "schemars", "serde", "serde_json", "settings", @@ -12852,6 +12856,7 @@ dependencies = [ "language", "project", "rand 0.8.5", + "schemars", "search", "serde", "serde_json", @@ -13186,6 +13191,7 @@ dependencies = [ "project", "remote", "rpc", + "schemars", "serde", "settings", "smallvec", diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index 33c12088a7d1fe..6ac1d851f2cb2b 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -26,7 +26,7 @@ pub use context::*; pub use context_store::*; use feature_flags::FeatureFlagAppExt; use fs::Fs; -use gpui::impl_actions; +use gpui::impl_internal_actions; use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal}; pub(crate) use inline_assistant::*; use language_model::{ @@ -74,13 +74,13 @@ actions!( ] ); -#[derive(PartialEq, Clone, Deserialize)] +#[derive(PartialEq, Clone)] pub enum InsertDraggedFiles { ProjectPaths(Vec), ExternalFiles(Vec), } -impl_actions!(assistant, [InsertDraggedFiles]); +impl_internal_actions!(assistant, [InsertDraggedFiles]); const DEFAULT_CONTEXT_LINES: usize = 50; diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index ede5916b06fa6a..43226f0f62a878 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -1,82 +1,84 @@ //! This module contains all actions supported by [`Editor`]. use super::*; use gpui::{action_aliases, action_as}; +use schemars::JsonSchema; use util::serde::default_true; -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct SelectNext { #[serde(default)] pub replace_newest: bool, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct SelectPrevious { #[serde(default)] pub replace_newest: bool, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct MoveToBeginningOfLine { #[serde(default = "default_true")] pub stop_at_soft_wraps: bool, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct SelectToBeginningOfLine { #[serde(default)] pub(super) stop_at_soft_wraps: bool, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct MovePageUp { #[serde(default)] pub(super) center_cursor: bool, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct MovePageDown { #[serde(default)] pub(super) center_cursor: bool, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct MoveToEndOfLine { #[serde(default = "default_true")] pub stop_at_soft_wraps: bool, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct SelectToEndOfLine { #[serde(default)] pub(super) stop_at_soft_wraps: bool, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct ToggleCodeActions { // Display row from which the action was deployed. #[serde(default)] + #[serde(skip)] pub deployed_from_indicator: Option, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct ConfirmCompletion { #[serde(default)] pub item_ix: Option, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct ComposeCompletion { #[serde(default)] pub item_ix: Option, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct ConfirmCodeAction { #[serde(default)] pub item_ix: Option, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct ToggleComments { #[serde(default)] pub advance_downwards: bool, @@ -84,84 +86,87 @@ pub struct ToggleComments { pub ignore_indent: bool, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct FoldAt { + #[serde(skip)] pub buffer_row: MultiBufferRow, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct UnfoldAt { + #[serde(skip)] pub buffer_row: MultiBufferRow, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct MoveUpByLines { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct MoveDownByLines { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct SelectUpByLines { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct SelectDownByLines { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct ExpandExcerpts { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct ExpandExcerptsUp { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct ExpandExcerptsDown { #[serde(default)] pub(super) lines: u32, } -#[derive(PartialEq, Clone, Deserialize, Default)] + +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct ShowCompletions { #[serde(default)] pub(super) trigger: Option, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct HandleInput(pub String); -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct DeleteToNextWordEnd { #[serde(default)] pub ignore_newlines: bool, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct DeleteToPreviousWordStart { #[serde(default)] pub ignore_newlines: bool, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct FoldAtLevel { pub level: u32, } -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct SpawnNearestTask { #[serde(default)] pub reveal: task::RevealStrategy, diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index 338397a5513f1b..bc7388e513ee1e 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -63,6 +63,16 @@ pub trait Action: 'static + Send { where Self: Sized; + /// Optional JSON schema for the action's input data. + fn action_json_schema( + _: &mut schemars::gen::SchemaGenerator, + ) -> Option + where + Self: Sized, + { + None + } + /// A list of alternate, deprecated names for this action. fn deprecated_aliases() -> &'static [&'static str] where @@ -90,16 +100,16 @@ impl dyn Action { type ActionBuilder = fn(json: serde_json::Value) -> anyhow::Result>; pub(crate) struct ActionRegistry { - builders_by_name: HashMap, + by_name: HashMap, names_by_type_id: HashMap, all_names: Vec, // So we can return a static slice. - deprecations: Vec<(SharedString, SharedString)>, + deprecations: HashMap, } impl Default for ActionRegistry { fn default() -> Self { let mut this = ActionRegistry { - builders_by_name: Default::default(), + by_name: Default::default(), names_by_type_id: Default::default(), all_names: Default::default(), deprecations: Default::default(), @@ -111,19 +121,25 @@ impl Default for ActionRegistry { } } +struct ActionData { + pub build: ActionBuilder, + pub json_schema: fn(&mut schemars::gen::SchemaGenerator) -> Option, +} + /// 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 type MacroActionBuilder = fn() -> 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 ActionData { +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::gen::SchemaGenerator) -> Option, } /// This constant must be public to be accessible from other crates. @@ -143,20 +159,35 @@ impl ActionRegistry { #[cfg(test)] pub(crate) fn load_action(&mut self) { - self.insert_action(ActionData { + self.insert_action(MacroActionData { name: A::debug_name(), aliases: A::deprecated_aliases(), type_id: TypeId::of::(), 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(); - 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 { - self.builders_by_name.insert(alias.into(), action.build); - self.deprecations.push((alias.into(), name.clone())); + let alias: SharedString = alias.into(); + 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.all_names.push(name); @@ -180,9 +211,10 @@ impl ActionRegistry { params: Option, ) -> Result> { let build_action = self - .builders_by_name + .by_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!({}))) .with_context(|| format!("Attempting to build action {}", name)) } @@ -191,12 +223,30 @@ impl ActionRegistry { self.all_names.as_slice() } - pub fn action_deprecations(&self) -> &[(SharedString, SharedString)] { - self.deprecations.as_slice() + pub fn action_schemas( + &self, + generator: &mut schemars::gen::SchemaGenerator, + ) -> Vec<(SharedString, Option)> { + // 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::>() + } + + pub fn action_deprecations(&self) -> &HashMap { + &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!` #[macro_export] macro_rules! actions { @@ -211,6 +261,11 @@ macro_rules! actions { gpui::__impl_action!($namespace, $name, $name, fn build(_: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box> { Ok(Box::new(Self)) + }, + fn action_json_schema( + _: &mut gpui::private::schemars::gen::SchemaGenerator, + ) -> Option { + None } ); @@ -219,11 +274,10 @@ macro_rules! actions { }; } -/// Defines a unit struct that can be used as an actions, with a name -/// that differs from it's type 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!` +/// 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) => { @@ -241,6 +295,11 @@ macro_rules! action_as { _: gpui::private::serde_json::Value, ) -> gpui::Result<::std::boxed::Box> { Ok(Box::new(Self)) + }, + fn action_json_schema( + generator: &mut gpui::private::schemars::gen::SchemaGenerator, + ) -> Option { + 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_rules! action_aliases { ($namespace:path, $name:ident, [$($alias:ident),* $(,)?]) => { @@ -261,6 +320,7 @@ macro_rules! action_aliases { ::std::default::Default, ::std::fmt::Debug, gpui::private::serde_derive::Deserialize, + gpui::private::schemars::JsonSchema, )] #[serde(crate = "gpui::private::serde")] pub struct $name; @@ -274,6 +334,12 @@ macro_rules! action_aliases { ) -> gpui::Result<::std::boxed::Box> { Ok(Box::new(Self)) }, + fn action_json_schema( + generator: &mut gpui::private::schemars::gen::SchemaGenerator, + ) -> Option { + None + + }, fn deprecated_aliases() -> &'static [&'static str] { &[ $(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_rules! impl_actions { ($namespace:path, [ $($name:ident),* $(,)? ]) => { @@ -293,6 +363,13 @@ macro_rules! impl_actions { gpui::__impl_action!($namespace, $name, $name, fn build(value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box> { Ok(std::boxed::Box::new(gpui::private::serde_json::from_value::(value)?)) + }, + fn action_json_schema( + generator: &mut gpui::private::schemars::gen::SchemaGenerator, + ) -> Option { + Some(::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 -/// Allows you to rename the action visually, without changing the struct's name +/// Implements the Action trait for internal action structs that implement Clone, Default, +/// PartialEq. The purpose of this is to conveniently define values that can be passed in `dyn +/// Action`. +/// +/// These actions are internal and so are not registered and do not support deserialization. +#[macro_export] +macro_rules! impl_internal_actions { + ($namespace:path, [ $($name:ident),* $(,)? ]) => { + $( + gpui::__impl_action!($namespace, $name, $name, + fn build(value: gpui::private::serde_json::Value) -> gpui::Result<::std::boxed::Box> { + gpui::Result::Err(gpui::private::anyhow::anyhow!( + concat!( + stringify!($namespace), + "::", + stringify!($visual_name), + " is an internal action, so cannot be built from JSON." + ))) + }, + fn action_json_schema( + generator: &mut gpui::private::schemars::gen::SchemaGenerator, + ) -> Option { + None + } + ); + )* + }; +} + +/// Implements the Action trait for a struct that implements Clone, Default, PartialEq, and +/// serde_deserialize::Deserialize. Allows you to rename the action visually, without changing the +/// struct's name. +/// +/// Fields and variants that don't make sense for user configuration should be annotated with +/// #[serde(skip)]. #[macro_export] macro_rules! impl_action_as { ($namespace:path, $name:ident as $visual_name:tt ) => { @@ -316,6 +426,13 @@ macro_rules! impl_action_as { Ok(std::boxed::Box::new( gpui::private::serde_json::from_value::(value)?, )) + }, + fn action_json_schema( + generator: &mut gpui::private::schemars::gen::SchemaGenerator, + ) -> Option { + Some(::json_schema( + generator, + )) } ); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index a0ec1f9933f112..0c23eaeb3bb6b8 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -23,7 +23,7 @@ use parking_lot::RwLock; use slotmap::SlotMap; pub use async_context::*; -use collections::{FxHashMap, FxHashSet, VecDeque}; +use collections::{FxHashMap, FxHashSet, HashMap, VecDeque}; pub use entity_map::*; use http_client::HttpClient; pub use model_context::*; @@ -1218,16 +1218,22 @@ impl AppContext { self.actions.build_action(name, data) } - /// Get a list of all action names that have been registered. - /// in the application. Note that registration only allows for - /// actions to be built dynamically, and is unrelated to binding - /// actions in the element tree. + /// 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] { 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)> { + self.actions.action_schemas(generator) + } + /// 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 { self.actions.action_deprecations() } diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index fd2617f393f9f4..b173382dcab466 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -102,7 +102,9 @@ mod window; /// Do not touch, here be dragons for use by gpui_macros and such. #[doc(hidden)] pub mod private { + pub use anyhow; pub use linkme; + pub use schemars; pub use serde; pub use serde_derive; pub use serde_json; diff --git a/crates/gpui/tests/action_macros.rs b/crates/gpui/tests/action_macros.rs index 99572a4b3c5b1c..98a70daa9532eb 100644 --- a/crates/gpui/tests/action_macros.rs +++ b/crates/gpui/tests/action_macros.rs @@ -1,12 +1,13 @@ use gpui::{actions, impl_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)] + #[derive(PartialEq, Clone, Deserialize, JsonSchema)] struct AnotherTestAction; impl_actions!(test, [AnotherTestAction]); diff --git a/crates/gpui_macros/src/register_action.rs b/crates/gpui_macros/src/register_action.rs index 7fc8158e9bf2c8..6c22ccf02fbd7a 100644 --- a/crates/gpui_macros/src/register_action.rs +++ b/crates/gpui_macros/src/register_action.rs @@ -29,12 +29,13 @@ pub(crate) fn register_action(type_name: &Ident) -> proc_macro2::TokenStream { fn __autogenerated() { /// This is an auto generated function, do not use. #[doc(hidden)] - fn #action_builder_fn_name() -> gpui::ActionData { - gpui::ActionData { + 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(), 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, } } #[doc(hidden)] diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 793513e025fee1..9732939d506860 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -662,8 +662,7 @@ pub struct LanguageConfigOverride { pub line_comments: Override>>, #[serde(default)] pub block_comment: Override<(Arc, Arc)>, - #[serde(skip_deserializing)] - #[schemars(skip)] + #[serde(skip)] pub disabled_bracket_ixs: Vec, #[serde(default)] pub word_characters: Override>, @@ -776,7 +775,7 @@ pub struct BracketPairConfig { pub pairs: Vec, /// 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]` - #[schemars(skip)] + #[serde(skip)] pub disabled_scopes_by_bracket_ix: Vec>, } diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index 951423056e6415..fffe0698554a85 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -56,6 +56,7 @@ project.workspace = true regex.workspace = true rope.workspace = true rust-embed.workspace = true +schemars.workspace = true serde.workspace = true serde_json.workspace = true settings.workspace = true diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index 7172f96f74430e..07a28d6abd04a4 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -10,6 +10,7 @@ use language::{LanguageRegistry, LanguageToolchainStore, LspAdapter, LspAdapterD use lsp::{LanguageServerBinary, LanguageServerName}; use node_runtime::NodeRuntime; use project::{lsp_store::language_server_settings, ContextProviderWithTasks}; +use schemars::gen::SchemaSettings; use serde_json::{json, Value}; use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore}; use smol::{ @@ -75,9 +76,6 @@ impl JsonLspAdapter { } fn get_workspace_config(language_names: Vec, 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 settings_schema = cx.global::().json_schema( &SettingsJsonSchemaParams { @@ -117,7 +115,7 @@ impl JsonLspAdapter { }, { "fileMatch": [schema_file_match(paths::keymap_file())], - "schema": KeymapFile::generate_json_schema(action_names, deprecations), + "schema": Self::generate_keymap_schema(cx), }, { "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)] diff --git a/crates/picker/Cargo.toml b/crates/picker/Cargo.toml index db42c1cd80cdf9..e906b0965e9d9e 100644 --- a/crates/picker/Cargo.toml +++ b/crates/picker/Cargo.toml @@ -20,6 +20,7 @@ anyhow.workspace = true editor.workspace = true gpui.workspace = true menu.workspace = true +schemars.workspace = true serde.workspace = true ui.workspace = true workspace.workspace = true diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index c97fceeef34033..56dbf69835b387 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -7,6 +7,7 @@ use gpui::{ ViewContext, WindowContext, }; use head::Head; +use schemars::JsonSchema; use serde::Deserialize; use std::{sync::Arc, time::Duration}; 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, /// performing some kind of action on it. -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(Clone, PartialEq, Deserialize, JsonSchema, Default)] pub struct ConfirmInput { pub secondary: bool, } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 1eccfc9e1b409e..5cdbd78ede508d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1,10 +1,10 @@ mod project_panel_settings; mod utils; +use anyhow::{anyhow, Context as _, Result}; use client::{ErrorCode, ErrorExt}; -use language::DiagnosticSeverity; -use settings::{Settings, SettingsStore}; - +use collections::{hash_map, BTreeSet, HashMap}; +use command_palette_hooks::CommandPaletteFilter; use db::kvp::KEY_VALUE_STORE; use editor::{ items::{ @@ -15,10 +15,6 @@ use editor::{ Editor, EditorEvent, EditorSettings, ShowScrollbar, }; 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 gpui::{ actions, anchored, deferred, div, impl_actions, point, px, size, uniform_list, Action, @@ -30,6 +26,7 @@ use gpui::{ VisualContext as _, WeakView, WindowContext, }; use indexmap::IndexMap; +use language::DiagnosticSeverity; use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; use project::{ relativize_path, Entry, EntryKind, Fs, Project, ProjectEntryId, ProjectPath, Worktree, @@ -38,7 +35,9 @@ use project::{ use project_panel_settings::{ ProjectPanelDockPosition, ProjectPanelSettings, ShowDiagnostics, ShowIndentGuides, }; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use settings::{Settings, SettingsStore}; use smallvec::SmallVec; use std::any::TypeId; use std::{ @@ -152,13 +151,13 @@ struct EntryDetails { canonical_path: Option>, } -#[derive(PartialEq, Clone, Default, Debug, Deserialize)] +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] struct Delete { #[serde(default)] pub skip_prompt: bool, } -#[derive(PartialEq, Clone, Default, Debug, Deserialize)] +#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] struct Trash { #[serde(default)] pub skip_prompt: bool, diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 18cdb36f169b61..ad36242d7cb326 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -31,6 +31,7 @@ gpui.workspace = true language.workspace = true menu.workspace = true project.workspace = true +schemars.workspace = true serde.workspace = true serde_json.workspace = true settings.workspace = true diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index a81ddc1a6a9c2a..df6e7e7d6a8f8d 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -22,6 +22,7 @@ use project::{ search::SearchQuery, search_history::{SearchHistory, SearchHistoryCursor}, }; +use schemars::JsonSchema; use serde::Deserialize; use settings::Settings; use std::sync::Arc; @@ -43,7 +44,7 @@ use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults}; const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50; -#[derive(PartialEq, Clone, Deserialize)] +#[derive(PartialEq, Clone, Deserialize, JsonSchema)] pub struct Deploy { #[serde(default = "util::serde::default_true")] pub focus: bool, diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index c2b4625ffc74ca..9b8a17c23e18c2 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -1,11 +1,11 @@ use crate::{settings_store::parse_json_with_comments, SettingsAssets}; use anyhow::{anyhow, Context, Result}; -use collections::BTreeMap; +use collections::{BTreeMap, HashMap}; use gpui::{Action, AppContext, KeyBinding, SharedString}; use schemars::{ - gen::{SchemaGenerator, SchemaSettings}, - schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation}, - JsonSchema, Map, + gen::SchemaGenerator, + schema::{ArrayValidation, InstanceType, Metadata, Schema, SchemaObject, SubschemaValidation}, + JsonSchema, }; use serde::Deserialize; use serde_json::Value; @@ -140,55 +140,117 @@ impl KeymapFile { } pub fn generate_json_schema( - action_names: &[SharedString], - deprecations: &[(SharedString, SharedString)], + generator: SchemaGenerator, + action_schemas: Vec<(SharedString, Option)>, + deprecations: &HashMap, ) -> serde_json::Value { - let mut root_schema = SchemaSettings::draft07() - .with(|settings| settings.option_add_null_type = false) - .into_generator() - .into_root_schema_for::(); - - 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))), + fn set(input: I) -> Option + where + I: Into, + { + Some(input.into()) + } + + 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() - }), - ]; - 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(), - )]), + }; + 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()); + } + } } - let action_schema = Schema::Object(SchemaObject { - subschemas: Some(Box::new(SubschemaValidation { - one_of: Some(alternatives), + + // 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::(); root_schema .definitions .insert("KeymapAction".to_owned(), action_schema); diff --git a/crates/tab_switcher/Cargo.toml b/crates/tab_switcher/Cargo.toml index c30469fb176c77..cee167ed7b5ff6 100644 --- a/crates/tab_switcher/Cargo.toml +++ b/crates/tab_switcher/Cargo.toml @@ -19,6 +19,7 @@ gpui.workspace = true menu.workspace = true picker.workspace = true project.workspace = true +schemars.workspace = true serde.workspace = true settings.workspace = true ui.workspace = true diff --git a/crates/tab_switcher/src/tab_switcher.rs b/crates/tab_switcher/src/tab_switcher.rs index f076a4f1bc5a61..1b427d9f31a591 100644 --- a/crates/tab_switcher/src/tab_switcher.rs +++ b/crates/tab_switcher/src/tab_switcher.rs @@ -10,6 +10,7 @@ use gpui::{ }; use picker::{Picker, PickerDelegate}; use project::Project; +use schemars::JsonSchema; use serde::Deserialize; use settings::Settings; use std::sync::Arc; @@ -23,7 +24,7 @@ use workspace::{ const PANEL_WIDTH_REMS: f32 = 28.; -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, JsonSchema, Default)] pub struct Toggle { #[serde(default)] pub select_last: bool, diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index 83803ff21c9761..b71c095abc3dba 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -29,6 +29,7 @@ itertools.workspace = true language.workspace = true project.workspace = true task.workspace = true +schemars.workspace = true search.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 63958bd8390401..6d87b03d0562f5 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -15,6 +15,7 @@ use gpui::{ use language::Bias; use persistence::TERMINAL_DB; use project::{search::SearchQuery, terminals::TerminalKind, Fs, Metadata, Project}; +use schemars::JsonSchema; use terminal::{ alacritty_terminal::{ index::Point, @@ -66,14 +67,14 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); 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)] pub struct ScrollTerminal(pub i32); -#[derive(Clone, Debug, Default, Deserialize, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq)] pub struct SendText(String); -#[derive(Clone, Debug, Default, Deserialize, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq)] pub struct SendKeystroke(String); impl_actions!(terminal, [SendText, SendKeystroke]); diff --git a/crates/title_bar/Cargo.toml b/crates/title_bar/Cargo.toml index df6e0afd174249..18980300b28328 100644 --- a/crates/title_bar/Cargo.toml +++ b/crates/title_bar/Cargo.toml @@ -36,6 +36,7 @@ notifications.workspace = true project.workspace = true remote.workspace = true rpc.workspace = true +schemars.workspace = true serde.workspace = true settings.workspace = true smallvec.workspace = true diff --git a/crates/title_bar/src/application_menu.rs b/crates/title_bar/src/application_menu.rs index 1c3e67c0958fdd..23e470a345444c 100644 --- a/crates/title_bar/src/application_menu.rs +++ b/crates/title_bar/src/application_menu.rs @@ -1,17 +1,18 @@ use gpui::{impl_actions, OwnedMenu, OwnedMenuItem, View}; +use schemars::JsonSchema; use serde::Deserialize; use smallvec::SmallVec; use ui::{prelude::*, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip}; impl_actions!( app_menu, - [OpenApplicationMenu, NavigateApplicationMenuInDirection,] + [OpenApplicationMenu, NavigateApplicationMenuInDirection] ); -#[derive(Clone, Deserialize, PartialEq, Default)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Default)] pub struct OpenApplicationMenu(String); -#[derive(Clone, Deserialize, PartialEq, Default)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq, Default)] pub struct NavigateApplicationMenuInDirection(String); #[derive(Clone)] diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index f7af6d0567cef0..e2bd19af5fb3c5 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -1,11 +1,3 @@ -use std::{ - iter::Peekable, - ops::{Deref, Range}, - str::Chars, - sync::OnceLock, - time::Instant, -}; - use anyhow::{anyhow, Result}; use command_palette_hooks::CommandInterceptResult; use editor::{ @@ -13,12 +5,22 @@ use editor::{ display_map::ToDisplayPoint, 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 multi_buffer::MultiBufferRow; use regex::Regex; +use schemars::JsonSchema; use search::{BufferSearchBar, SearchOptions}; use serde::Deserialize; +use std::{ + iter::Peekable, + ops::{Deref, Range}, + str::Chars, + sync::OnceLock, + time::Instant, +}; use util::ResultExt; use workspace::{notifications::NotifyResultExt, SaveIntent}; @@ -33,24 +35,24 @@ use crate::{ Vim, }; -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct GoToLine { range: CommandRange, } -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct YankCommand { range: CommandRange, } -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct WithRange { restore_selection: bool, range: CommandRange, action: WrappedAction, } -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct WithCount { count: u32, action: WrappedAction, @@ -60,20 +62,11 @@ pub struct WithCount { struct WrappedAction(Box); actions!(vim, [VisualCommand, CountCommand]); -impl_actions!( +impl_internal_actions!( vim, [GoToLine, YankCommand, WithRange, WithCount, OnMatchingLines] ); -impl<'de> Deserialize<'de> for WrappedAction { - fn deserialize(_: D) -> Result - where - D: serde::Deserializer<'de>, - { - Err(serde::de::Error::custom("Cannot deserialize WrappedAction")) - } -} - impl PartialEq for WrappedAction { fn eq(&self, other: &Self) -> bool { 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 { Line { row: u32, 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 { start: Position, end: Option, @@ -877,7 +870,7 @@ fn generate_positions(string: &str, query: &str) -> Vec { positions } -#[derive(Debug, PartialEq, Deserialize, Clone)] +#[derive(Debug, PartialEq, Clone)] pub(crate) struct OnMatchingLines { range: CommandRange, search: String, diff --git a/crates/vim/src/digraph.rs b/crates/vim/src/digraph.rs index dcccc8b5cd316e..c17a774be04ec3 100644 --- a/crates/vim/src/digraph.rs +++ b/crates/vim/src/digraph.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use collections::HashMap; use editor::Editor; use gpui::{impl_actions, AppContext, Keystroke, KeystrokeEvent}; +use schemars::JsonSchema; use serde::Deserialize; use settings::Settings; use std::sync::LazyLock; @@ -12,7 +13,7 @@ use crate::{state::Operator, Vim, VimSettings}; mod default; -#[derive(PartialEq, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, JsonSchema, PartialEq)] struct Literal(String, char); impl_actions!(vim, [Literal]); diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 43c4397e8b272c..cf90d03a7ded77 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -9,6 +9,7 @@ use editor::{ use gpui::{actions, impl_actions, px, ViewContext}; use language::{CharKind, Point, Selection, SelectionGoal}; use multi_buffer::MultiBufferRow; +use schemars::JsonSchema; use serde::Deserialize; use std::ops::Range; use workspace::searchable::Direction; @@ -139,105 +140,105 @@ pub enum Motion { }, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] struct NextWordStart { #[serde(default)] ignore_punctuation: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] struct NextWordEnd { #[serde(default)] ignore_punctuation: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] struct PreviousWordStart { #[serde(default)] ignore_punctuation: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] struct PreviousWordEnd { #[serde(default)] ignore_punctuation: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] pub(crate) struct NextSubwordStart { #[serde(default)] pub(crate) ignore_punctuation: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] pub(crate) struct NextSubwordEnd { #[serde(default)] pub(crate) ignore_punctuation: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] pub(crate) struct PreviousSubwordStart { #[serde(default)] pub(crate) ignore_punctuation: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] pub(crate) struct PreviousSubwordEnd { #[serde(default)] pub(crate) ignore_punctuation: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] pub(crate) struct Up { #[serde(default)] pub(crate) display_lines: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] pub(crate) struct Down { #[serde(default)] pub(crate) display_lines: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] struct FirstNonWhitespace { #[serde(default)] display_lines: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] struct EndOfLine { #[serde(default)] display_lines: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] pub struct StartOfLine { #[serde(default)] pub(crate) display_lines: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] struct UnmatchedForward { #[serde(default)] char: char, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] struct UnmatchedBackward { #[serde(default)] diff --git a/crates/vim/src/normal/increment.rs b/crates/vim/src/normal/increment.rs index ca300fc1be27d1..803a8d01e28bf0 100644 --- a/crates/vim/src/normal/increment.rs +++ b/crates/vim/src/normal/increment.rs @@ -1,20 +1,20 @@ -use std::ops::Range; - use editor::{scroll::Autoscroll, Editor, MultiBufferSnapshot, ToOffset, ToPoint}; use gpui::{impl_actions, ViewContext}; use language::{Bias, Point}; +use schemars::JsonSchema; use serde::Deserialize; +use std::ops::Range; use crate::{state::Mode, Vim}; -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] struct Increment { #[serde(default)] step: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] struct Decrement { #[serde(default)] diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 8d49a6802c1952..4fe18a61b1266d 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -1,16 +1,16 @@ -use std::cmp; - use editor::{display_map::ToDisplayPoint, movement, scroll::Autoscroll, DisplayPoint, RowExt}; use gpui::{impl_actions, ViewContext}; use language::{Bias, SelectionGoal}; +use schemars::JsonSchema; use serde::Deserialize; +use std::cmp; use crate::{ state::{Mode, Register}, Vim, }; -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Paste { #[serde(default)] diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 103d33f8af12ce..fb6bd6a93b4ae8 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -1,10 +1,10 @@ -use std::{iter::Peekable, str::Chars, time::Duration}; - use editor::Editor; -use gpui::{actions, impl_actions, ViewContext}; +use gpui::{actions, impl_actions, impl_internal_actions, ViewContext}; use language::Point; +use schemars::JsonSchema; use search::{buffer_search, BufferSearchBar, SearchOptions}; use serde_derive::Deserialize; +use std::{iter::Peekable, str::Chars, time::Duration}; use util::serde::default_true; use workspace::{notifications::NotifyResultExt, searchable::Direction}; @@ -15,7 +15,7 @@ use crate::{ Vim, }; -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] pub(crate) struct MoveToNext { #[serde(default = "default_true")] @@ -26,7 +26,7 @@ pub(crate) struct MoveToNext { regex: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] pub(crate) struct MoveToPrev { #[serde(default = "default_true")] @@ -37,7 +37,7 @@ pub(crate) struct MoveToPrev { regex: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)] pub(crate) struct Search { #[serde(default)] backwards: bool, @@ -45,19 +45,19 @@ pub(crate) struct Search { regex: bool, } -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)] pub struct FindCommand { pub query: String, pub backwards: bool, } -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq)] pub struct ReplaceCommand { pub(crate) range: CommandRange, pub(crate) replacement: Replacement, } -#[derive(Debug, Default, PartialEq, Deserialize, Clone)] +#[derive(Clone, Debug, PartialEq)] pub(crate) struct Replacement { search: String, replacement: String, @@ -66,10 +66,8 @@ pub(crate) struct Replacement { } actions!(vim, [SearchSubmit, MoveToNextMatch, MoveToPrevMatch]); -impl_actions!( - vim, - [FindCommand, ReplaceCommand, Search, MoveToPrev, MoveToNext] -); +impl_actions!(vim, [FindCommand, Search, MoveToPrev, MoveToNext]); +impl_internal_actions!(vim, [ReplaceCommand]); pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext) { Vim::action(editor, cx, Vim::move_to_next); diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 745d1adb784186..a00e6fea17e8d4 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -10,15 +10,14 @@ use editor::{ movement::{self, FindRange}, Bias, DisplayPoint, Editor, }; - -use itertools::Itertools; - use gpui::{actions, impl_actions, ViewContext}; +use itertools::Itertools; use language::{BufferSnapshot, CharKind, Point, Selection, TextObject, TreeSitterOptions}; use multi_buffer::MultiBufferRow; +use schemars::JsonSchema; use serde::Deserialize; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, JsonSchema)] pub enum Object { Word { ignore_punctuation: bool }, Sentence, @@ -40,13 +39,14 @@ pub enum Object { Comment, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] struct Word { #[serde(default)] ignore_punctuation: bool, } -#[derive(Clone, Deserialize, PartialEq)] + +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] struct IndentObj { #[serde(default)] diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index f7a617a6ab55f0..e401903c9a9b9d 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -1,6 +1,3 @@ -use std::borrow::BorrowMut; -use std::{fmt::Display, ops::Range, sync::Arc}; - use crate::command::command_interceptor; use crate::normal::repeat::Replayer; use crate::surrounds::SurroundsType; @@ -13,12 +10,15 @@ use gpui::{ Action, AppContext, BorrowAppContext, ClipboardEntry, ClipboardItem, Global, View, WeakView, }; use language::Point; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; +use std::borrow::BorrowMut; +use std::{fmt::Display, ops::Range, sync::Arc}; use ui::{SharedString, ViewContext}; use workspace::searchable::Direction; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, JsonSchema, Serialize)] pub enum Mode { Normal, 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 { Change, Delete, Yank, Replace, - Object { around: bool }, - FindForward { before: bool }, - FindBackward { after: bool }, - Sneak { first_char: Option }, - SneakBackward { first_char: Option }, - AddSurrounds { target: Option }, - ChangeSurrounds { target: Option }, + Object { + around: bool, + }, + FindForward { + before: bool, + }, + FindBackward { + after: bool, + }, + Sneak { + first_char: Option, + }, + SneakBackward { + first_char: Option, + }, + AddSurrounds { + #[serde(skip)] + target: Option, + }, + ChangeSurrounds { + target: Option, + }, DeleteSurrounds, Mark, - Jump { line: bool }, + Jump { + line: bool, + }, Indent, Outdent, AutoIndent, @@ -82,8 +99,12 @@ pub enum Operator { Lowercase, Uppercase, OppositeCase, - Digraph { first_char: Option }, - Literal { prefix: Option }, + Digraph { + first_char: Option, + }, + Literal { + prefix: Option, + }, Register, RecordRegister, ReplayRegister, diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index 719a1470623861..a480ff3617bab1 100644 --- a/crates/vim/src/surrounds.rs +++ b/crates/vim/src/surrounds.rs @@ -6,7 +6,7 @@ use crate::{ }; use editor::{movement, scroll::Autoscroll, Bias}; use language::BracketPair; -use serde::Deserialize; + use std::sync::Arc; use ui::ViewContext; @@ -17,16 +17,6 @@ pub enum SurroundsType { Selection, } -// This exists so that we can have Deserialize on Operators, but not on Motions. -impl<'de> Deserialize<'de> for SurroundsType { - fn deserialize(_: D) -> Result - where - D: serde::Deserializer<'de>, - { - Err(serde::de::Error::custom("Cannot deserialize SurroundsType")) - } -} - impl Vim { pub fn add_surrounds( &mut self, diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index dbc3a25ce320c6..d8a58e4789374e 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -49,25 +49,25 @@ use workspace::{self, Pane, ResizeIntent, Workspace}; use crate::state::ReplayableAction; /// Used to resize the current pane -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] pub struct ResizePane(pub ResizeIntent); /// An Action to Switch between modes -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] pub struct SwitchMode(pub Mode); /// PushOperator is used to put vim into a "minor" mode, /// where it's waiting for a specific next set of keystrokes. /// For example 'd' needs a motion to complete. -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] pub struct PushOperator(pub Operator); /// Number is used to manage vim's count. Pushing a digit -/// multiplis the current value by 10 and adds the digit. -#[derive(Clone, Deserialize, PartialEq)] +/// multiplies the current value by 10 and adds the digit. +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] struct Number(usize); -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, JsonSchema, PartialEq)] struct SelectRegister(String); actions!( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 05ab9a8f90e2bd..a633c27610b047 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -25,6 +25,7 @@ use itertools::Itertools; use language::DiagnosticSeverity; use parking_lot::Mutex; use project::{Project, ProjectEntryId, ProjectPath, WorktreeId}; +use schemars::JsonSchema; use serde::Deserialize; use settings::{Settings, SettingsStore}; 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")] pub enum SaveIntent { /// write all files (even if unchanged) @@ -92,16 +93,16 @@ pub enum SaveIntent { Skip, } -#[derive(Clone, Deserialize, PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] pub struct ActivateItem(pub usize); -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] #[serde(rename_all = "camelCase")] pub struct CloseActiveItem { pub save_intent: Option, } -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] #[serde(rename_all = "camelCase")] pub struct CloseInactiveItems { pub save_intent: Option, @@ -109,7 +110,7 @@ pub struct CloseInactiveItems { pub close_pinned: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] #[serde(rename_all = "camelCase")] pub struct CloseAllItems { pub save_intent: Option, @@ -117,34 +118,35 @@ pub struct CloseAllItems { pub close_pinned: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] #[serde(rename_all = "camelCase")] pub struct CloseCleanItems { #[serde(default)] pub close_pinned: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] #[serde(rename_all = "camelCase")] pub struct CloseItemsToTheRight { #[serde(default)] pub close_pinned: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] #[serde(rename_all = "camelCase")] pub struct CloseItemsToTheLeft { #[serde(default)] pub close_pinned: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] #[serde(rename_all = "camelCase")] pub struct RevealInProjectPanel { + #[serde(skip)] pub entry_id: Option, } -#[derive(Default, PartialEq, Clone, Deserialize)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default)] pub struct DeploySearch { #[serde(default)] pub replace_enabled: bool, diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 168f6539e01001..1b523c3d2ca79e 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -13,6 +13,7 @@ use gpui::{ }; use parking_lot::Mutex; use project::Project; +use schemars::JsonSchema; use serde::Deserialize; use settings::Settings; 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 { Up, Down, @@ -800,7 +801,7 @@ impl SplitDirection { } } -#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq)] pub enum ResizeIntent { Lengthen, Shorten, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ad91bb386b009a..bc5d801e06a819 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -61,10 +61,9 @@ use persistence::{ SerializedWindowBounds, DB, }; use postage::stream::Stream; -use project::{ - DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId, -}; +use project::{DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree}; use remote::{ssh_session::ConnectionIdentifier, SshClientDelegate, SshConnectionOptions}; +use schemars::JsonSchema; use serde::Deserialize; use session::AppSession; use settings::Settings; @@ -119,9 +118,6 @@ static ZED_WINDOW_POSITION: LazyLock>> = LazyLock::new(|| { .and_then(parse_pixel_position_env_var) }); -#[derive(Clone, PartialEq)] -pub struct RemoveWorktreeFromProject(pub WorktreeId); - actions!(assistant, [ShowConfiguration]); actions!( @@ -165,64 +161,64 @@ pub struct OpenPaths { pub paths: Vec, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, PartialEq, JsonSchema)] pub struct ActivatePane(pub usize); -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, PartialEq, JsonSchema)] pub struct ActivatePaneInDirection(pub SplitDirection); -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, PartialEq, JsonSchema)] pub struct SwapPaneInDirection(pub SplitDirection); -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, PartialEq, JsonSchema)] pub struct MoveItemToPane { pub destination: usize, #[serde(default = "default_true")] pub focus: bool, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, PartialEq, JsonSchema)] pub struct MoveItemToPaneInDirection { pub direction: SplitDirection, #[serde(default = "default_true")] pub focus: bool, } -#[derive(Clone, PartialEq, Debug, Deserialize)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct SaveAll { pub save_intent: Option, } -#[derive(Clone, PartialEq, Debug, Deserialize)] +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct Save { pub save_intent: Option, } -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct CloseAllItemsAndPanes { pub save_intent: Option, } -#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +#[derive(Clone, PartialEq, Debug, Deserialize, Default, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct CloseInactiveTabsAndPanes { pub save_intent: Option, } -#[derive(Clone, Deserialize, PartialEq)] +#[derive(Clone, Deserialize, PartialEq, JsonSchema)] pub struct SendKeystrokes(pub String); -#[derive(Clone, Deserialize, PartialEq, Default)] +#[derive(Clone, Deserialize, PartialEq, Default, JsonSchema)] pub struct Reload { pub binary_path: Option, } action_as!(project_symbols, ToggleProjectSymbols as Toggle); -#[derive(Default, PartialEq, Eq, Clone, serde::Deserialize)] +#[derive(Default, PartialEq, Eq, Clone, Deserialize, JsonSchema)] pub struct ToggleFileFinder { #[serde(default)] 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 working_directory: PathBuf, } diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 6b2691cf76b357..cafc46b743ee61 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -11,12 +11,12 @@ use serde::{Deserialize, Serialize}; // https://github.com/mmastrac/rust-ctor/issues/280 pub fn init() {} -#[derive(Clone, PartialEq, Deserialize)] +#[derive(Clone, PartialEq, Deserialize, JsonSchema)] pub struct OpenBrowser { pub url: String, } -#[derive(Clone, PartialEq, Deserialize)] +#[derive(Clone, PartialEq, Deserialize, JsonSchema)] pub struct OpenZedUrl { pub url: String, } @@ -65,9 +65,10 @@ pub mod feedback { pub mod theme_selector { use gpui::impl_actions; + use schemars::JsonSchema; use serde::Deserialize; - #[derive(PartialEq, Clone, Default, Debug, Deserialize)] + #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)] pub struct Toggle { /// A list of theme names to filter the theme selector down to. pub themes_filter: Option>, @@ -76,20 +77,21 @@ pub mod theme_selector { impl_actions!(theme_selector, [Toggle]); } -#[derive(Clone, Default, Deserialize, PartialEq)] +#[derive(Clone, Default, Deserialize, PartialEq, JsonSchema)] pub struct InlineAssist { pub prompt: Option, } impl_actions!(assistant, [InlineAssist]); -#[derive(PartialEq, Clone, Deserialize, Default)] +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct OpenRecent { #[serde(default)] 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. #[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] @@ -102,8 +104,8 @@ pub enum RevealTarget { Dock, } -/// Spawn a task with name or open tasks modal -#[derive(Debug, PartialEq, Clone, Deserialize)] +/// Spawn a task with name or open tasks modal. +#[derive(Debug, PartialEq, Clone, Deserialize, JsonSchema)] #[serde(untagged)] pub enum Spawn { /// Spawns a task by the name given. @@ -128,8 +130,8 @@ impl Spawn { } } -/// Rerun last task -#[derive(PartialEq, Clone, Deserialize, Default)] +/// Rerun the last task. +#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)] pub struct Rerun { /// 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 @@ -147,6 +149,7 @@ pub struct Rerun { pub use_new_terminal: Option, /// If present, rerun the task with this ID, otherwise rerun the last task. + #[serde(skip)] pub task_id: Option, }