@@ -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"],
@@ -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>) {
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>) {
+ 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>,
+ ) {
+ 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<Self>) {
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;