Working change and delete in line mode

Keith Simmons created

Change summary

assets/keymaps/vim.json                    |  2 +-
crates/editor/src/element.rs               |  2 +-
crates/editor/src/selections_collection.rs |  2 +-
crates/vim/src/editor_events.rs            |  2 +-
crates/vim/src/state.rs                    |  4 ++++
crates/vim/src/vim.rs                      | 10 ++++++++++
crates/vim/src/visual.rs                   | 12 +++++++++---
7 files changed, 27 insertions(+), 7 deletions(-)

Detailed changes

assets/keymaps/vim.json 🔗

@@ -73,7 +73,7 @@
                 "vim::SwitchMode",
                 "Visual"
             ],
-            "V": [
+            "shift-V": [
                 "vim::SwitchMode",
                 "VisualLine"
             ]

crates/editor/src/element.rs 🔗

@@ -488,7 +488,7 @@ impl EditorElement {
         line_mode: bool,
         cx: &mut PaintContext,
     ) {
-        if range.start != range.end {
+        if range.start != range.end || line_mode {
             let row_range = if range.end.column() == 0 {
                 cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
             } else {

crates/editor/src/selections_collection.rs 🔗

@@ -38,7 +38,7 @@ impl SelectionsCollection {
             display_map,
             buffer,
             next_selection_id: 1,
-            line_mode: true,
+            line_mode: false,
             disjoint: Arc::from([]),
             pending: Some(PendingSelection {
                 selection: Selection {

crates/vim/src/editor_events.rs 🔗

@@ -22,7 +22,7 @@ fn editor_focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppCont
         vim.active_editor = Some(editor.downgrade());
         vim.selection_subscription = Some(cx.subscribe(editor, |editor, event, cx| {
             if let editor::Event::SelectionsChanged { local: true } = event {
-                let newest_empty = !editor.read(cx).selections.newest::<usize>(cx).is_empty();
+                let newest_empty = editor.read(cx).selections.newest::<usize>(cx).is_empty();
                 editor_local_selections_changed(newest_empty, cx);
             }
         }));

crates/vim/src/state.rs 🔗

@@ -46,6 +46,10 @@ impl VimState {
         !matches!(self.mode, Mode::Insert)
     }
 
+    pub fn empty_selections_only(&self) -> bool {
+        self.mode != Mode::Visual && self.mode != Mode::VisualLine
+    }
+
     pub fn keymap_context_layer(&self) -> Context {
         let mut context = Context::default();
         context.map.insert(

crates/vim/src/vim.rs 🔗

@@ -128,14 +128,24 @@ impl Vim {
                         editor.set_cursor_shape(cursor_shape, cx);
                         editor.set_clip_at_line_ends(cursor_shape == CursorShape::Block, cx);
                         editor.set_input_enabled(!state.vim_controlled());
+                        editor.selections.line_mode = state.mode == Mode::VisualLine;
                         let context_layer = state.keymap_context_layer();
                         editor.set_keymap_context_layer::<Self>(context_layer);
                     } else {
                         editor.set_cursor_shape(CursorShape::Bar, cx);
                         editor.set_clip_at_line_ends(false, cx);
                         editor.set_input_enabled(true);
+                        editor.selections.line_mode = false;
                         editor.remove_keymap_context_layer::<Self>();
                     }
+
+                    if state.empty_selections_only() {
+                        editor.change_selections(None, cx, |s| {
+                            s.move_with(|_, selection| {
+                                selection.collapse_to(selection.head(), selection.goal)
+                            });
+                        })
+                    }
                 });
             }
         }

crates/vim/src/visual.rs 🔗

@@ -1,3 +1,4 @@
+use collections::HashMap;
 use editor::{Autoscroll, Bias};
 use gpui::{actions, MutableAppContext, ViewContext};
 use workspace::Workspace;
@@ -68,7 +69,7 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspac
     });
 }
 
-pub fn change_line(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspace>) {
+pub fn change_line(_: &mut Workspace, _: &VisualLineChange, cx: &mut ViewContext<Workspace>) {
     Vim::update(cx, |vim, cx| {
         vim.update_active_editor(cx, |editor, cx| {
             editor.set_clip_at_line_ends(false, cx);
@@ -114,13 +115,14 @@ pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspac
     });
 }
 
-pub fn delete_line(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspace>) {
+pub fn delete_line(_: &mut Workspace, _: &VisualLineDelete, cx: &mut ViewContext<Workspace>) {
     Vim::update(cx, |vim, cx| {
-        vim.switch_mode(Mode::Normal, cx);
         vim.update_active_editor(cx, |editor, cx| {
             editor.set_clip_at_line_ends(false, cx);
+            let mut original_columns: HashMap<_, _> = Default::default();
             editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
                 s.move_with(|map, selection| {
+                    original_columns.insert(selection.id, selection.head().column());
                     selection.start = map.prev_line_boundary(selection.start.to_point(map)).1;
 
                     if selection.end.row() < map.max_point().row() {
@@ -143,11 +145,15 @@ pub fn delete_line(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Wor
             editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
                 s.move_with(|map, selection| {
                     let mut cursor = selection.head();
+                    if let Some(column) = original_columns.get(&selection.id) {
+                        *cursor.column_mut() = *column
+                    }
                     cursor = map.clip_point(cursor, Bias::Left);
                     selection.collapse_to(cursor, selection.goal)
                 });
             });
         });
+        vim.switch_mode(Mode::Normal, cx);
     });
 }