diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index c7e6199f44..da094ea7e4 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -371,6 +371,7 @@ "Replace" ], "s": "vim::Substitute", + "shift-s": "vim::SubstituteLine", "> >": "editor::Indent", "< <": "editor::Outdent", "ctrl-pagedown": "pane::ActivateNextItem", @@ -446,6 +447,7 @@ } ], "s": "vim::Substitute", + "shift-s": "vim::SubstituteLine", "c": "vim::Substitute", "~": "vim::ChangeCase", "shift-i": [ diff --git a/crates/live_kit_client/LiveKitBridge/Package.resolved b/crates/live_kit_client/LiveKitBridge/Package.resolved index b925bc8f0d..85ae088565 100644 --- a/crates/live_kit_client/LiveKitBridge/Package.resolved +++ b/crates/live_kit_client/LiveKitBridge/Package.resolved @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/apple/swift-protobuf.git", "state": { "branch": null, - "revision": "ce20dc083ee485524b802669890291c0d8090170", - "version": "1.22.1" + "revision": "0af9125c4eae12a4973fb66574c53a54962a9e1e", + "version": "1.21.0" } } ] diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index a73c518809..1f8276c327 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -27,7 +27,6 @@ use self::{ case::change_case, change::{change_motion, change_object}, delete::{delete_motion, delete_object}, - substitute::substitute, yank::{yank_motion, yank_object}, }; @@ -44,7 +43,6 @@ actions!( ChangeToEndOfLine, DeleteToEndOfLine, Yank, - Substitute, ChangeCase, ] ); @@ -56,13 +54,8 @@ pub fn init(cx: &mut AppContext) { cx.add_action(insert_line_above); cx.add_action(insert_line_below); cx.add_action(change_case); + substitute::init(cx); search::init(cx); - cx.add_action(|_: &mut Workspace, _: &Substitute, cx| { - Vim::update(cx, |vim, cx| { - let times = vim.pop_number_operator(cx); - substitute(vim, times, cx); - }) - }); cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| { Vim::update(cx, |vim, cx| { let times = vim.pop_number_operator(cx); diff --git a/crates/vim/src/normal/substitute.rs b/crates/vim/src/normal/substitute.rs index b04596240a..efdd43d0a4 100644 --- a/crates/vim/src/normal/substitute.rs +++ b/crates/vim/src/normal/substitute.rs @@ -1,10 +1,32 @@ -use gpui::WindowContext; +use editor::movement; +use gpui::{actions, AppContext, WindowContext}; use language::Point; +use workspace::Workspace; use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim}; -pub fn substitute(vim: &mut Vim, count: Option, cx: &mut WindowContext) { - let line_mode = vim.state().mode == Mode::VisualLine; +actions!(vim, [Substitute, SubstituteLine]); + +pub(crate) fn init(cx: &mut AppContext) { + cx.add_action(|_: &mut Workspace, _: &Substitute, cx| { + Vim::update(cx, |vim, cx| { + let times = vim.pop_number_operator(cx); + substitute(vim, times, vim.state().mode == Mode::VisualLine, cx); + }) + }); + + cx.add_action(|_: &mut Workspace, _: &SubstituteLine, cx| { + Vim::update(cx, |vim, cx| { + if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) { + vim.switch_mode(Mode::VisualLine, false, cx) + } + let count = vim.pop_number_operator(cx); + substitute(vim, count, true, cx) + }) + }); +} + +pub fn substitute(vim: &mut Vim, count: Option, line_mode: bool, cx: &mut WindowContext) { vim.update_active_editor(cx, |editor, cx| { editor.set_clip_at_line_ends(false, cx); editor.transact(cx, |editor, cx| { @@ -14,6 +36,11 @@ pub fn substitute(vim: &mut Vim, count: Option, cx: &mut WindowContext) { Motion::Right.expand_selection(map, selection, count, true); } if line_mode { + // in Visual mode when the selection contains the newline at the end + // of the line, we should exclude it. + if !selection.is_empty() && selection.end.column() == 0 { + selection.end = movement::left(map, selection.end); + } Motion::CurrentLine.expand_selection(map, selection, None, false); if let Some((point, _)) = (Motion::FirstNonWhitespace { display_lines: false, @@ -166,4 +193,68 @@ mod test { the laˇzy dog"}) .await; } + + #[gpui::test] + async fn test_substitute_line(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + let initial_state = indoc! {" + The quick brown + fox juˇmps over + the lazy dog + "}; + + // normal mode + cx.set_shared_state(initial_state).await; + cx.simulate_shared_keystrokes(["shift-s", "o"]).await; + cx.assert_shared_state(indoc! {" + The quick brown + oˇ + the lazy dog + "}) + .await; + + // visual mode + cx.set_shared_state(initial_state).await; + cx.simulate_shared_keystrokes(["v", "k", "shift-s", "o"]) + .await; + cx.assert_shared_state(indoc! {" + oˇ + the lazy dog + "}) + .await; + + // visual block mode + cx.set_shared_state(initial_state).await; + cx.simulate_shared_keystrokes(["ctrl-v", "j", "shift-s", "o"]) + .await; + cx.assert_shared_state(indoc! {" + The quick brown + oˇ + "}) + .await; + + // visual mode including newline + cx.set_shared_state(initial_state).await; + cx.simulate_shared_keystrokes(["v", "$", "shift-s", "o"]) + .await; + cx.assert_shared_state(indoc! {" + The quick brown + oˇ + the lazy dog + "}) + .await; + + // indentation + cx.set_neovim_option("shiftwidth=4").await; + cx.set_shared_state(initial_state).await; + cx.simulate_shared_keystrokes([">", ">", "shift-s", "o"]) + .await; + cx.assert_shared_state(indoc! {" + The quick brown + oˇ + the lazy dog + "}) + .await; + } } diff --git a/crates/vim/test_data/test_substitute_line.json b/crates/vim/test_data/test_substitute_line.json new file mode 100644 index 0000000000..eb0a9825f8 --- /dev/null +++ b/crates/vim/test_data/test_substitute_line.json @@ -0,0 +1,29 @@ +{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}} +{"Key":"shift-s"} +{"Key":"o"} +{"Get":{"state":"The quick brown\noˇ\nthe lazy dog\n","mode":"Insert"}} +{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}} +{"Key":"v"} +{"Key":"k"} +{"Key":"shift-s"} +{"Key":"o"} +{"Get":{"state":"oˇ\nthe lazy dog\n","mode":"Insert"}} +{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}} +{"Key":"ctrl-v"} +{"Key":"j"} +{"Key":"shift-s"} +{"Key":"o"} +{"Get":{"state":"The quick brown\noˇ\n","mode":"Insert"}} +{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}} +{"Key":"v"} +{"Key":"$"} +{"Key":"shift-s"} +{"Key":"o"} +{"Get":{"state":"The quick brown\noˇ\nthe lazy dog\n","mode":"Insert"}} +{"SetOption":{"value":"shiftwidth=4"}} +{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}} +{"Key":">"} +{"Key":">"} +{"Key":"shift-s"} +{"Key":"o"} +{"Get":{"state":"The quick brown\n oˇ\nthe lazy dog\n","mode":"Insert"}}