From ed367e1636b6ed1851b7b37bdf58eccb0d84370a Mon Sep 17 00:00:00 2001 From: 5brian Date: Mon, 28 Apr 2025 14:14:43 -0400 Subject: [PATCH] vim: Add neovim 0.11 default mappings (#28602) Update the keymap to include: https://neovim.io/doc/user/news-0.11.html#_defaults This does conflict with `gr` replace with register though, is `gR` a good alternative? Release Notes: - vim: Update the keymap to include: https://neovim.io/doc/user/news-0.11.html#_defaults - vim: Replace with register has been remapped from `gr` to `gR`. --- assets/keymaps/vim.json | 23 +++-- crates/vim/src/normal.rs | 84 +++++++++++++++++++ crates/vim/src/normal/paste.rs | 12 +-- crates/vim/src/state.rs | 2 +- .../test_insert_empty_line_above.json | 24 ++++++ docs/src/vim.md | 2 +- 6 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 crates/vim/test_data/test_insert_empty_line_above.json diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 18fb97d052e1e4d063289801892752dbf39e8e5b..04a911d2dfe02158aee61c17efaf22e3d7ebe3c4 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -50,6 +50,12 @@ "] -": "vim::NextLesserIndent", "] +": "vim::NextGreaterIndent", "] =": "vim::NextSameIndent", + "] b": "pane::ActivateNextItem", + "[ b": "pane::ActivatePreviousItem", + "] shift-b": "pane::ActivateLastItem", + "[ shift-b": ["pane::ActivateItem", 0], + "] space": "vim::InsertEmptyLineBelow", + "[ space": "vim::InsertEmptyLineAbove", // Word motions "w": "vim::NextWordStart", "e": "vim::NextWordEnd", @@ -108,7 +114,11 @@ "ctrl-e": "vim::LineDown", "ctrl-y": "vim::LineUp", // "g" commands - "g r": "vim::PushReplaceWithRegister", + "g shift-r": "vim::PushReplaceWithRegister", + "g r n": "editor::Rename", + "g r r": "editor::FindAllReferences", + "g r i": "editor::GoToImplementation", + "g r a": "editor::ToggleCodeActions", "g g": "vim::StartOfDocument", "g h": "editor::Hover", "g t": "pane::ActivateNextItem", @@ -127,6 +137,7 @@ "g <": ["editor::SelectPrevious", { "replace_newest": true }], "g a": "editor::SelectAllMatches", "g s": "outline::Toggle", + "g shift-o": "outline::Toggle", "g shift-s": "project_symbols::Toggle", "g .": "editor::ToggleCodeActions", // zed specific "g shift-a": "editor::FindAllReferences", // zed specific @@ -305,7 +316,7 @@ "!": "vim::ShellCommand", "i": ["vim::PushObject", { "around": false }], "a": ["vim::PushObject", { "around": true }], - "g r": ["vim::Paste", { "preserve_clipboard": true }], + "g shift-r": ["vim::Paste", { "preserve_clipboard": true }], "g c": "vim::ToggleComments", "g q": "vim::Rewrap", "g ?": "vim::ConvertToRot13", @@ -339,7 +350,8 @@ "ctrl-shift-q": ["vim::PushLiteral", {}], "ctrl-r": "vim::PushRegister", "insert": "vim::ToggleReplace", - "ctrl-o": "vim::TemporaryNormal" + "ctrl-o": "vim::TemporaryNormal", + "ctrl-s": "editor::ShowSignatureHelp" } }, { @@ -630,9 +642,10 @@ } }, { - "context": "vim_operator == gr", + "context": "vim_operator == gR", "bindings": { - "r": "vim::CurrentLine" + "r": "vim::CurrentLine", + "shift-r": "vim::CurrentLine" } }, { diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 7781891050468d4cbe122115be453c5c11944b83..f0d24772dd36b37d5f868b7179392184e84bef21 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -43,6 +43,8 @@ actions!( InsertEndOfLine, InsertLineAbove, InsertLineBelow, + InsertEmptyLineAbove, + InsertEmptyLineBelow, InsertAtPrevious, JoinLines, JoinLinesNoWhitespace, @@ -72,6 +74,8 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, Vim::insert_end_of_line); Vim::action(editor, cx, Vim::insert_line_above); Vim::action(editor, cx, Vim::insert_line_below); + Vim::action(editor, cx, Vim::insert_empty_line_above); + Vim::action(editor, cx, Vim::insert_empty_line_below); Vim::action(editor, cx, Vim::insert_at_previous); Vim::action(editor, cx, Vim::change_case); Vim::action(editor, cx, Vim::convert_to_upper_case); @@ -537,6 +541,61 @@ impl Vim { }); } + fn insert_empty_line_above( + &mut self, + _: &InsertEmptyLineAbove, + window: &mut Window, + cx: &mut Context, + ) { + self.record_current_action(cx); + self.update_editor(window, cx, |_, editor, window, cx| { + editor.transact(window, cx, |editor, _, cx| { + let selections = editor.selections.all::(cx); + + let selection_start_rows: BTreeSet = selections + .into_iter() + .map(|selection| selection.start.row) + .collect(); + let edits = selection_start_rows + .into_iter() + .map(|row| { + let start_of_line = Point::new(row, 0); + (start_of_line..start_of_line, "\n".to_string()) + }) + .collect::>(); + editor.edit(edits, cx); + }); + }); + } + + fn insert_empty_line_below( + &mut self, + _: &InsertEmptyLineBelow, + window: &mut Window, + cx: &mut Context, + ) { + self.record_current_action(cx); + self.update_editor(window, cx, |_, editor, window, cx| { + editor.transact(window, cx, |editor, _, cx| { + let selections = editor.selections.all::(cx); + let snapshot = editor.buffer().read(cx).snapshot(cx); + + let selection_end_rows: BTreeSet = selections + .into_iter() + .map(|selection| selection.end.row) + .collect(); + let edits = selection_end_rows + .into_iter() + .map(|row| { + let end_of_line = Point::new(row, snapshot.line_len(MultiBufferRow(row))); + (end_of_line..end_of_line, "\n".to_string()) + }) + .collect::>(); + editor.edit(edits, cx); + }); + }); + } + fn join_lines_impl( &mut self, insert_whitespace: bool, @@ -1267,6 +1326,31 @@ mod test { ); } + #[gpui::test] + async fn test_insert_empty_line_above(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + cx.simulate("[ space", "ˇ").await.assert_matches(); + cx.simulate("[ space", "The ˇquick").await.assert_matches(); + cx.simulate_at_each_offset( + "[ space", + indoc! {" + The qˇuick + brown ˇfox + jumps ˇover"}, + ) + .await + .assert_matches(); + cx.simulate( + "[ space", + indoc! {" + The quick + ˇ + brown fox"}, + ) + .await + .assert_matches(); + } + #[gpui::test] async fn test_dd(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 3d0a3e44c85b4d99560a51c55aa26f2eb8f3974c..68b0acefacdc4b61d53862cfa6a2e6e73433ed11 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -917,7 +917,7 @@ mod test { ); cx.simulate_keystrokes("y i w"); cx.simulate_keystrokes("w"); - cx.simulate_keystrokes("g r i w"); + cx.simulate_keystrokes("g shift-r i w"); cx.assert_state( indoc! {" fish fisˇh @@ -925,7 +925,7 @@ mod test { "}, Mode::Normal, ); - cx.simulate_keystrokes("j b g r e"); + cx.simulate_keystrokes("j b g shift-r e"); cx.assert_state( indoc! {" fish fish @@ -945,7 +945,7 @@ mod test { ); cx.simulate_keystrokes("y i w"); cx.simulate_keystrokes("w"); - cx.simulate_keystrokes("v i w g r"); + cx.simulate_keystrokes("v i w g shift-r"); cx.assert_state( indoc! {" fish fisˇh @@ -953,7 +953,7 @@ mod test { "}, Mode::Normal, ); - cx.simulate_keystrokes("g r r"); + cx.simulate_keystrokes("g shift-r r"); cx.assert_state( indoc! {" fisˇh @@ -961,7 +961,7 @@ mod test { "}, Mode::Normal, ); - cx.simulate_keystrokes("j w g r $"); + cx.simulate_keystrokes("j w g shift-r $"); cx.assert_state( indoc! {" fish @@ -986,7 +986,7 @@ mod test { ); cx.simulate_keystrokes("y i w"); cx.simulate_keystrokes("w"); - cx.simulate_keystrokes("g r i w"); + cx.simulate_keystrokes("g shift-r i w"); cx.assert_state( indoc! {" fish fisˇh diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 8a38e598611c11c266b1b5e6789077e1eb14ace2..a6df5f33ba4c804873ad433735fe0556407da463 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -954,7 +954,7 @@ impl Operator { Operator::AutoIndent => "eq", Operator::ShellCommand => "sh", Operator::Rewrap => "gq", - Operator::ReplaceWithRegister => "gr", + Operator::ReplaceWithRegister => "gR", Operator::Exchange => "cx", Operator::Outdent => "<", Operator::Uppercase => "gU", diff --git a/crates/vim/test_data/test_insert_empty_line_above.json b/crates/vim/test_data/test_insert_empty_line_above.json new file mode 100644 index 0000000000000000000000000000000000000000..10e8f6dc0d7d70f3200dcacbe095ec9feb4df0f8 --- /dev/null +++ b/crates/vim/test_data/test_insert_empty_line_above.json @@ -0,0 +1,24 @@ +{"Put":{"state":"ˇ"}} +{"Key":"["} +{"Key":"space"} +{"Get":{"state":"\nˇ","mode":"Normal"}} +{"Put":{"state":"The ˇquick"}} +{"Key":"["} +{"Key":"space"} +{"Get":{"state":"\nThe ˇquick","mode":"Normal"}} +{"Put":{"state":"The qˇuick\nbrown fox\njumps over"}} +{"Key":"["} +{"Key":"space"} +{"Get":{"state":"\nThe qˇuick\nbrown fox\njumps over","mode":"Normal"}} +{"Put":{"state":"The quick\nbrown ˇfox\njumps over"}} +{"Key":"["} +{"Key":"space"} +{"Get":{"state":"The quick\n\nbrown ˇfox\njumps over","mode":"Normal"}} +{"Put":{"state":"The quick\nbrown fox\njumps ˇover"}} +{"Key":"["} +{"Key":"space"} +{"Get":{"state":"The quick\nbrown fox\n\njumps ˇover","mode":"Normal"}} +{"Put":{"state":"The quick\nˇ\nbrown fox"}} +{"Key":"["} +{"Key":"space"} +{"Get":{"state":"The quick\n\nˇ\nbrown fox","mode":"Normal"}} diff --git a/docs/src/vim.md b/docs/src/vim.md index 827c9c537dd246f9981f852d583069c85197154e..28deee3b0fd19873a8ed0b5e8f5257d42050e94d 100644 --- a/docs/src/vim.md +++ b/docs/src/vim.md @@ -164,7 +164,7 @@ Zed's vim mode includes some features that are usually provided by very popular - You can comment and uncomment selections with `gc` in visual mode and `gcc` in normal mode. - The project panel supports many shortcuts modeled after the Vim plugin `netrw`: navigation with `hjkl`, open file with `o`, open file in a new tab with `t`, etc. - You can add key bindings to your keymap to navigate "camelCase" names. [Head down to the Optional key bindings](#optional-key-bindings) section to learn how. -- You can use `gr` to do [ReplaceWithRegister](https://github.com/vim-scripts/ReplaceWithRegister). +- You can use `gR` to do [ReplaceWithRegister](https://github.com/vim-scripts/ReplaceWithRegister). - You can use `cx` for [vim-exchange](https://github.com/tommcdo/vim-exchange) functionality. Note that it does not have a default binding in visual mode, but you can add one to your keymap (refer to the [optional key bindings](#optional-key-bindings) section). - You can navigate to indent depths relative to your cursor with the [indent wise](https://github.com/jeetsukumaran/vim-indentwise) plugin `[-`, `]-`, `[+`, `]+`, `[=`, `]=`.