Migrate keymap and settings + edit predictions rename (#23834)

- [x] snake case keymap properties
- [x] flatten actions
- [x] keymap migration + notfication
- [x] settings migration + notification
- [x] inline completions -> edit predictions 

### future: 
- keymap notification doesn't show up on start up, only on keymap save.
this is existing bug in zed, will be addressed in seperate PR.

Release Notes:

- Added a notification for deprecated settings and keymaps, allowing you
to migrate them with a single click. A backup of your existing keymap
and settings will be created in your home directory.
- Modified some keymap actions and settings for consistency.

---------

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
smit 2025-02-07 21:17:07 +05:30 committed by GitHub
parent a1544f47ad
commit 00c2a30059
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 2106 additions and 617 deletions

View file

@ -141,105 +141,105 @@ pub enum Motion {
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct NextWordStart {
#[serde(default)]
ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct NextWordEnd {
#[serde(default)]
ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct PreviousWordStart {
#[serde(default)]
ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct PreviousWordEnd {
#[serde(default)]
ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct NextSubwordStart {
#[serde(default)]
pub(crate) ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct NextSubwordEnd {
#[serde(default)]
pub(crate) ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct PreviousSubwordStart {
#[serde(default)]
pub(crate) ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct PreviousSubwordEnd {
#[serde(default)]
pub(crate) ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct Up {
#[serde(default)]
pub(crate) display_lines: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct Down {
#[serde(default)]
pub(crate) display_lines: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct FirstNonWhitespace {
#[serde(default)]
display_lines: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct EndOfLine {
#[serde(default)]
display_lines: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct StartOfLine {
#[serde(default)]
pub(crate) display_lines: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct UnmatchedForward {
#[serde(default)]
char: char,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct UnmatchedBackward {
#[serde(default)]
char: char,

View file

@ -8,14 +8,14 @@ use std::ops::Range;
use crate::{state::Mode, Vim};
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct Increment {
#[serde(default)]
step: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct Decrement {
#[serde(default)]
step: bool,

View file

@ -13,7 +13,7 @@ use crate::{
};
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct Paste {
#[serde(default)]
before: bool,

View file

@ -16,7 +16,7 @@ use crate::{
};
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct MoveToNext {
#[serde(default = "default_true")]
case_sensitive: bool,
@ -27,7 +27,7 @@ pub(crate) struct MoveToNext {
}
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub(crate) struct MoveToPrev {
#[serde(default = "default_true")]
case_sensitive: bool,
@ -38,6 +38,7 @@ pub(crate) struct MoveToPrev {
}
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
pub(crate) struct Search {
#[serde(default)]
backwards: bool,
@ -46,6 +47,7 @@ pub(crate) struct Search {
}
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct FindCommand {
pub query: String,
pub backwards: bool,

View file

@ -19,6 +19,7 @@ use serde::Deserialize;
use ui::Context;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Object {
Word { ignore_punctuation: bool },
Subword { ignore_punctuation: bool },
@ -44,20 +45,20 @@ pub enum Object {
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct Word {
#[serde(default)]
ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct Subword {
#[serde(default)]
ignore_punctuation: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
struct IndentObj {
#[serde(default)]
include_below: bool,

View file

@ -10,7 +10,6 @@ use gpui::{
Action, App, BorrowAppContext, ClipboardEntry, ClipboardItem, Entity, Global, WeakEntity,
};
use language::Point;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
use std::borrow::BorrowMut;
@ -18,7 +17,7 @@ use std::{fmt::Display, ops::Range, sync::Arc};
use ui::{Context, SharedString};
use workspace::searchable::Direction;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, JsonSchema, Serialize)]
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum Mode {
Normal,
Insert,
@ -59,7 +58,7 @@ impl Default for Mode {
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, JsonSchema)]
#[derive(Clone, Debug, PartialEq)]
pub enum Operator {
Change,
Delete,
@ -82,7 +81,6 @@ pub enum Operator {
},
AddSurrounds {
// Typically no need to configure this as `SendKeystrokes` can be used - see #23088.
#[serde(skip)]
target: Option<SurroundsType>,
},
ChangeSurrounds {

View file

@ -554,11 +554,7 @@ mod test {
use gpui::KeyBinding;
use indoc::indoc;
use crate::{
state::{Mode, Operator},
test::VimTestContext,
PushOperator,
};
use crate::{state::Mode, test::VimTestContext, PushAddSurrounds};
#[gpui::test]
async fn test_add_surrounds(cx: &mut gpui::TestAppContext) {
@ -749,7 +745,7 @@ mod test {
cx.update(|_, cx| {
cx.bind_keys([KeyBinding::new(
"shift-s",
PushOperator(Operator::AddSurrounds { target: None }),
PushAddSurrounds {},
Some("vim_mode == visual"),
)])
});

View file

@ -17,12 +17,7 @@ use indoc::indoc;
use search::BufferSearchBar;
use workspace::WorkspaceSettings;
use crate::{
insert::NormalBefore,
motion,
state::{Mode, Operator},
PushOperator,
};
use crate::{insert::NormalBefore, motion, state::Mode, PushSneak, PushSneakBackward};
#[gpui::test]
async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
@ -1347,17 +1342,17 @@ async fn test_sneak(cx: &mut gpui::TestAppContext) {
cx.bind_keys([
KeyBinding::new(
"s",
PushOperator(Operator::Sneak { first_char: None }),
PushSneak { first_char: None },
Some("vim_mode == normal"),
),
KeyBinding::new(
"S",
PushOperator(Operator::SneakBackward { first_char: None }),
PushSneakBackward { first_char: None },
Some("vim_mode == normal"),
),
KeyBinding::new(
"S",
PushOperator(Operator::SneakBackward { first_char: None }),
PushSneakBackward { first_char: None },
Some("vim_mode == visual"),
),
])

View file

@ -35,6 +35,7 @@ use language::{CursorShape, Point, Selection, SelectionGoal, TransactionId};
pub use mode_indicator::ModeIndicator;
use motion::Motion;
use normal::search::SearchSubmit;
use object::Object;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
@ -45,24 +46,10 @@ use surrounds::SurroundsType;
use theme::ThemeSettings;
use ui::{px, IntoElement, SharedString};
use vim_mode_setting::VimModeSetting;
use workspace::{self, Pane, ResizeIntent, Workspace};
use workspace::{self, Pane, Workspace};
use crate::state::ReplayableAction;
/// Used to resize the current pane
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
pub struct ResizePane(pub ResizeIntent);
/// An Action to Switch between modes
#[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, JsonSchema, PartialEq)]
pub struct PushOperator(pub Operator);
/// Number is used to manage vim's count. Pushing a digit
/// multiplies the current value by 10 and adds the digit.
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
@ -71,29 +58,126 @@ struct Number(usize);
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
struct SelectRegister(String);
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushObject {
around: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushFindForward {
before: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushFindBackward {
after: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushSneak {
first_char: Option<char>,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushSneakBackward {
first_char: Option<char>,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushAddSurrounds {}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushChangeSurrounds {
target: Option<Object>,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushJump {
line: bool,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushDigraph {
first_char: Option<char>,
}
#[derive(Clone, Deserialize, JsonSchema, PartialEq)]
#[serde(deny_unknown_fields)]
struct PushLiteral {
prefix: Option<String>,
}
actions!(
vim,
[
SwitchToNormalMode,
SwitchToInsertMode,
SwitchToReplaceMode,
SwitchToVisualMode,
SwitchToVisualLineMode,
SwitchToVisualBlockMode,
SwitchToHelixNormalMode,
ClearOperators,
Tab,
Enter,
InnerObject,
FindForward,
FindBackward,
MaximizePane,
OpenDefaultKeymap,
ResetPaneSizes,
Sneak,
SneakBackward,
ResizePaneRight,
ResizePaneLeft,
ResizePaneUp,
ResizePaneDown,
PushChange,
PushDelete,
PushYank,
PushReplace,
PushDeleteSurrounds,
PushMark,
PushIndent,
PushOutdent,
PushAutoIndent,
PushRewrap,
PushShellCommand,
PushLowercase,
PushUppercase,
PushOppositeCase,
PushRegister,
PushRecordRegister,
PushReplayRegister,
PushReplaceWithRegister,
PushToggleComments,
]
);
// in the workspace namespace so it's not filtered out when vim is disabled.
actions!(workspace, [ToggleVimMode]);
actions!(workspace, [ToggleVimMode,]);
impl_actions!(
vim,
[ResizePane, SwitchMode, PushOperator, Number, SelectRegister]
[
Number,
SelectRegister,
PushObject,
PushFindForward,
PushFindBackward,
PushSneak,
PushSneakBackward,
PushAddSurrounds,
PushChangeSurrounds,
PushJump,
PushDigraph,
PushLiteral
]
);
/// Initializes the `vim` crate.
@ -142,7 +226,7 @@ pub fn init(cx: &mut App) {
workspace.resize_pane(Axis::Vertical, desired_size - size.size.height, window, cx)
});
workspace.register_action(|workspace, action: &ResizePane, window, cx| {
workspace.register_action(|workspace, _: &ResizePaneRight, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
let theme = ThemeSettings::get_global(cx);
let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {
@ -154,16 +238,36 @@ pub fn init(cx: &mut App) {
else {
return;
};
let height = theme.buffer_font_size() * theme.buffer_line_height.value();
workspace.resize_pane(Axis::Horizontal, width.width * count, window, cx);
});
let (axis, amount) = match action.0 {
ResizeIntent::Lengthen => (Axis::Vertical, height),
ResizeIntent::Shorten => (Axis::Vertical, height * -1.),
ResizeIntent::Widen => (Axis::Horizontal, width.width),
ResizeIntent::Narrow => (Axis::Horizontal, width.width * -1.),
workspace.register_action(|workspace, _: &ResizePaneLeft, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
let theme = ThemeSettings::get_global(cx);
let Ok(font_id) = window.text_system().font_id(&theme.buffer_font) else {
return;
};
let Ok(width) = window
.text_system()
.advance(font_id, theme.buffer_font_size(), 'm')
else {
return;
};
workspace.resize_pane(Axis::Horizontal, -width.width * count, window, cx);
});
workspace.resize_pane(axis, amount * count, window, cx);
workspace.register_action(|workspace, _: &ResizePaneUp, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
let theme = ThemeSettings::get_global(cx);
let height = theme.buffer_font_size() * theme.buffer_line_height.value();
workspace.resize_pane(Axis::Vertical, height * count, window, cx);
});
workspace.register_action(|workspace, _: &ResizePaneDown, window, cx| {
let count = Vim::take_count(cx).unwrap_or(1) as f32;
let theme = ThemeSettings::get_global(cx);
let height = theme.buffer_font_size() * theme.buffer_line_height.value();
workspace.resize_pane(Axis::Vertical, -height * count, window, cx);
});
workspace.register_action(|workspace, _: &SearchSubmit, window, cx| {
@ -330,12 +434,212 @@ impl Vim {
});
vim.update(cx, |_, cx| {
Vim::action(editor, cx, |vim, action: &SwitchMode, window, cx| {
vim.switch_mode(action.0, false, window, cx)
Vim::action(editor, cx, |vim, _: &SwitchToNormalMode, window, cx| {
vim.switch_mode(Mode::Normal, false, window, cx)
});
Vim::action(editor, cx, |vim, action: &PushOperator, window, cx| {
vim.push_operator(action.0.clone(), window, cx)
Vim::action(editor, cx, |vim, _: &SwitchToInsertMode, window, cx| {
vim.switch_mode(Mode::Insert, false, window, cx)
});
Vim::action(editor, cx, |vim, _: &SwitchToReplaceMode, window, cx| {
vim.switch_mode(Mode::Replace, false, window, cx)
});
Vim::action(editor, cx, |vim, _: &SwitchToVisualMode, window, cx| {
vim.switch_mode(Mode::Visual, false, window, cx)
});
Vim::action(editor, cx, |vim, _: &SwitchToVisualLineMode, window, cx| {
vim.switch_mode(Mode::VisualLine, false, window, cx)
});
Vim::action(
editor,
cx,
|vim, _: &SwitchToVisualBlockMode, window, cx| {
vim.switch_mode(Mode::VisualBlock, false, window, cx)
},
);
Vim::action(
editor,
cx,
|vim, _: &SwitchToHelixNormalMode, window, cx| {
vim.switch_mode(Mode::HelixNormal, false, window, cx)
},
);
Vim::action(editor, cx, |vim, action: &PushObject, window, cx| {
vim.push_operator(
Operator::Object {
around: action.around,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, action: &PushFindForward, window, cx| {
vim.push_operator(
Operator::FindForward {
before: action.before,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, action: &PushFindBackward, window, cx| {
vim.push_operator(
Operator::FindBackward {
after: action.after,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, action: &PushSneak, window, cx| {
vim.push_operator(
Operator::Sneak {
first_char: action.first_char,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, action: &PushSneakBackward, window, cx| {
vim.push_operator(
Operator::SneakBackward {
first_char: action.first_char,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, _: &PushAddSurrounds, window, cx| {
vim.push_operator(Operator::AddSurrounds { target: None }, window, cx)
});
Vim::action(
editor,
cx,
|vim, action: &PushChangeSurrounds, window, cx| {
vim.push_operator(
Operator::ChangeSurrounds {
target: action.target,
},
window,
cx,
)
},
);
Vim::action(editor, cx, |vim, action: &PushJump, window, cx| {
vim.push_operator(Operator::Jump { line: action.line }, window, cx)
});
Vim::action(editor, cx, |vim, action: &PushDigraph, window, cx| {
vim.push_operator(
Operator::Digraph {
first_char: action.first_char,
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, action: &PushLiteral, window, cx| {
vim.push_operator(
Operator::Literal {
prefix: action.prefix.clone(),
},
window,
cx,
)
});
Vim::action(editor, cx, |vim, _: &PushChange, window, cx| {
vim.push_operator(Operator::Change, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushDelete, window, cx| {
vim.push_operator(Operator::Delete, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushYank, window, cx| {
vim.push_operator(Operator::Yank, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushReplace, window, cx| {
vim.push_operator(Operator::Replace, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushDeleteSurrounds, window, cx| {
vim.push_operator(Operator::DeleteSurrounds, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushMark, window, cx| {
vim.push_operator(Operator::Mark, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushIndent, window, cx| {
vim.push_operator(Operator::Indent, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushOutdent, window, cx| {
vim.push_operator(Operator::Outdent, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushAutoIndent, window, cx| {
vim.push_operator(Operator::AutoIndent, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushRewrap, window, cx| {
vim.push_operator(Operator::Rewrap, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushShellCommand, window, cx| {
vim.push_operator(Operator::ShellCommand, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushLowercase, window, cx| {
vim.push_operator(Operator::Lowercase, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushUppercase, window, cx| {
vim.push_operator(Operator::Uppercase, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushOppositeCase, window, cx| {
vim.push_operator(Operator::OppositeCase, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushRegister, window, cx| {
vim.push_operator(Operator::Register, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushRecordRegister, window, cx| {
vim.push_operator(Operator::RecordRegister, window, cx)
});
Vim::action(editor, cx, |vim, _: &PushReplayRegister, window, cx| {
vim.push_operator(Operator::ReplayRegister, window, cx)
});
Vim::action(
editor,
cx,
|vim, _: &PushReplaceWithRegister, window, cx| {
vim.push_operator(Operator::ReplaceWithRegister, window, cx)
},
);
Vim::action(editor, cx, |vim, _: &PushToggleComments, window, cx| {
vim.push_operator(Operator::ToggleComments, window, cx)
});
Vim::action(editor, cx, |vim, _: &ClearOperators, window, cx| {
@ -1275,8 +1579,8 @@ impl Vim {
if self.mode == Mode::Normal {
self.update_editor(window, cx, |_, editor, window, cx| {
editor.accept_inline_completion(
&editor::actions::AcceptInlineCompletion {},
editor.accept_edit_prediction(
&editor::actions::AcceptEditPrediction {},
window,
cx,
);