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

View file

@ -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<dyn Action>);
actions!(vim, [VisualCommand, CountCommand]);
impl_actions!(
impl_internal_actions!(
vim,
[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 {
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<Position>,
@ -877,7 +870,7 @@ fn generate_positions(string: &str, query: &str) -> Vec<usize> {
positions
}
#[derive(Debug, PartialEq, Deserialize, Clone)]
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct OnMatchingLines {
range: CommandRange,
search: String,

View file

@ -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]);

View file

@ -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)]

View file

@ -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)]

View file

@ -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)]

View file

@ -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>) {
Vim::action(editor, cx, Vim::move_to_next);

View file

@ -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)]

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::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<char> },
SneakBackward { first_char: Option<char> },
AddSurrounds { target: Option<SurroundsType> },
ChangeSurrounds { target: Option<Object> },
Object {
around: bool,
},
FindForward {
before: bool,
},
FindBackward {
after: bool,
},
Sneak {
first_char: Option<char>,
},
SneakBackward {
first_char: Option<char>,
},
AddSurrounds {
#[serde(skip)]
target: Option<SurroundsType>,
},
ChangeSurrounds {
target: Option<Object>,
},
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<char> },
Literal { prefix: Option<String> },
Digraph {
first_char: Option<char>,
},
Literal {
prefix: Option<String>,
},
Register,
RecordRegister,
ReplayRegister,

View file

@ -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>(_: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Err(serde::de::Error::custom("Cannot deserialize SurroundsType"))
}
}
impl Vim {
pub fn add_surrounds(
&mut self,

View file

@ -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!(