diff --git a/crates/vim/src/normal/substitute.rs b/crates/vim/src/normal/substitute.rs index bfd2af0481..1d53c6831c 100644 --- a/crates/vim/src/normal/substitute.rs +++ b/crates/vim/src/normal/substitute.rs @@ -5,8 +5,8 @@ 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; - vim.switch_mode(Mode::Insert, true, cx); vim.update_active_editor(cx, |editor, cx| { + editor.set_clip_at_line_ends(false, cx); editor.transact(cx, |editor, cx| { editor.change_selections(None, cx, |s| { s.move_with(|map, selection| { @@ -32,6 +32,7 @@ pub fn substitute(vim: &mut Vim, count: Option, cx: &mut WindowContext) { editor.edit(edits, cx); }); }); + vim.switch_mode(Mode::Insert, true, cx); } #[cfg(test)] diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index ca22d25012..e41fab5495 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -232,7 +232,7 @@ impl Vim { s.move_with(|map, selection| { if last_mode.is_visual() && !mode.is_visual() { let mut point = selection.head(); - if !selection.reversed { + if !selection.reversed && !selection.is_empty() { point = movement::left(map, selection.head()); } selection.collapse_to(point, selection.goal) diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 866086d538..4065657e59 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -157,9 +157,12 @@ pub fn visual_block_motion( let columns = if is_reversed { head.column()..tail.column() + } else if head.column() == tail.column() { + head.column()..(head.column() + 1) } else { tail.column()..head.column() }; + let mut selections = Vec::new(); let mut row = tail.row(); @@ -972,6 +975,61 @@ mod test { .await; } + #[gpui::test] + async fn test_visual_block_insert(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(indoc! { + "ˇThe quick brown + fox jumps over + the lazy dog + " + }) + .await; + cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await; + cx.assert_shared_state(indoc! { + "«Tˇ»he quick brown + «fˇ»ox jumps over + «tˇ»he lazy dog + ˇ" + }) + .await; + + cx.simulate_shared_keystrokes(["shift-i", "k", "escape"]) + .await; + cx.assert_shared_state(indoc! { + "ˇkThe quick brown + kfox jumps over + kthe lazy dog + k" + }) + .await; + + cx.set_shared_state(indoc! { + "ˇThe quick brown + fox jumps over + the lazy dog + " + }) + .await; + cx.simulate_shared_keystrokes(["ctrl-v", "9", "down"]).await; + cx.assert_shared_state(indoc! { + "«Tˇ»he quick brown + «fˇ»ox jumps over + «tˇ»he lazy dog + ˇ" + }) + .await; + cx.simulate_shared_keystrokes(["c", "k", "escape"]).await; + cx.assert_shared_state(indoc! { + "ˇkhe quick brown + kox jumps over + khe lazy dog + k" + }) + .await; + } + #[gpui::test] async fn test_mode_across_command(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; diff --git a/crates/vim/test_data/test_visual_block_insert.json b/crates/vim/test_data/test_visual_block_insert.json new file mode 100644 index 0000000000..d3d2689bd3 --- /dev/null +++ b/crates/vim/test_data/test_visual_block_insert.json @@ -0,0 +1,18 @@ +{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog\n"}} +{"Key":"ctrl-v"} +{"Key":"9"} +{"Key":"down"} +{"Get":{"state":"«Tˇ»he quick brown\n«fˇ»ox jumps over\n«tˇ»he lazy dog\nˇ","mode":"VisualBlock"}} +{"Key":"shift-i"} +{"Key":"k"} +{"Key":"escape"} +{"Get":{"state":"ˇkThe quick brown\nkfox jumps over\nkthe lazy dog\nk","mode":"Normal"}} +{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog\n"}} +{"Key":"ctrl-v"} +{"Key":"9"} +{"Key":"down"} +{"Get":{"state":"«Tˇ»he quick brown\n«fˇ»ox jumps over\n«tˇ»he lazy dog\nˇ","mode":"VisualBlock"}} +{"Key":"c"} +{"Key":"k"} +{"Key":"escape"} +{"Get":{"state":"ˇkhe quick brown\nkox jumps over\nkhe lazy dog\nk","mode":"Normal"}}