From 4a4d8c1cabcf8f898cdba496e08b2b27a4386922 Mon Sep 17 00:00:00 2001 From: "Sergio C." Date: Tue, 24 Sep 2024 13:21:57 -0300 Subject: [PATCH] vim: Add ability to spawn multicursors at beginning/end of line (#18183) Closes #17842 Release Notes: - Added the ability to spawn multiple cursors through the g-A and g-I motions while in visual select mode. --- assets/keymaps/vim.json | 2 + crates/vim/src/visual.rs | 85 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 8d933f19afb1dd466f28e825bb842aba6ca1f1be..6656ea0ddf22c3d1addcba031aff8ea68ce2ee8d 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -292,6 +292,8 @@ "g ctrl-x": ["vim::Decrement", { "step": true }], "shift-i": "vim::InsertBefore", "shift-a": "vim::InsertAfter", + "g I": "vim::VisualInsertFirstNonWhiteSpace", + "g A": "vim::VisualInsertEndOfLine", "shift-j": "vim::JoinLines", "r": ["vim::PushOperator", "Replace"], "ctrl-c": ["vim::SwitchMode", "Normal"], diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 55dc7885200b049dca6d89f85df29b5b87ebf2a5..1503eaac1b6b436d6b4f22b11ac525d2f1a6434b 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -15,7 +15,7 @@ use util::ResultExt; use workspace::searchable::Direction; use crate::{ - motion::{start_of_line, Motion}, + motion::{first_non_whitespace, next_line_end, start_of_line, Motion}, object::Object, state::{Mode, Operator}, Vim, @@ -37,6 +37,8 @@ actions!( SelectNextMatch, SelectPreviousMatch, RestoreVisualSelection, + VisualInsertEndOfLine, + VisualInsertFirstNonWhiteSpace, ] ); @@ -51,6 +53,8 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext) { vim.toggle_mode(Mode::VisualBlock, cx) }); Vim::action(editor, cx, Vim::other_end); + Vim::action(editor, cx, Vim::visual_insert_end_of_line); + Vim::action(editor, cx, Vim::visual_insert_first_non_white_space); Vim::action(editor, cx, |vim, _: &VisualDelete, cx| { vim.record_current_action(cx); vim.visual_delete(false, cx); @@ -374,6 +378,39 @@ impl Vim { } } + fn visual_insert_end_of_line(&mut self, _: &VisualInsertEndOfLine, cx: &mut ViewContext) { + self.update_editor(cx, |_, editor, cx| { + editor.split_selection_into_lines(&Default::default(), cx); + editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_cursors_with(|map, cursor, _| { + (next_line_end(map, cursor, 1), SelectionGoal::None) + }); + }); + }); + + self.switch_mode(Mode::Insert, false, cx); + } + + fn visual_insert_first_non_white_space( + &mut self, + _: &VisualInsertFirstNonWhiteSpace, + cx: &mut ViewContext, + ) { + self.update_editor(cx, |_, editor, cx| { + editor.split_selection_into_lines(&Default::default(), cx); + editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.move_cursors_with(|map, cursor, _| { + ( + first_non_whitespace(map, false, cursor), + SelectionGoal::None, + ) + }); + }); + }); + + self.switch_mode(Mode::Insert, false, cx); + } + fn toggle_mode(&mut self, mode: Mode, cx: &mut ViewContext) { if self.mode == mode { self.switch_mode(Mode::Normal, false, cx); @@ -714,6 +751,52 @@ mod test { ˇ"}); } + #[gpui::test] + async fn test_visual_insert_first_non_whitespace(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state( + indoc! { + "«The quick brown + fox jumps over + the lazy dogˇ»" + }, + Mode::Visual, + ); + cx.simulate_keystrokes("g I"); + cx.assert_state( + indoc! { + "ˇThe quick brown + ˇfox jumps over + ˇthe lazy dog" + }, + Mode::Insert, + ); + } + + #[gpui::test] + async fn test_visual_insert_end_of_line(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state( + indoc! { + "«The quick brown + fox jumps over + the lazy dogˇ»" + }, + Mode::Visual, + ); + cx.simulate_keystrokes("g A"); + cx.assert_state( + indoc! { + "The quick brownˇ + fox jumps overˇ + the lazy dogˇ" + }, + Mode::Insert, + ); + } + #[gpui::test] async fn test_enter_visual_line_mode(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await;