Fix clipping when dragging the mouse with vim-mode enabled and adjust single line editor selections

K Simmons created

Change summary

crates/editor/src/element.rs       | 10 +++++-----
crates/gpui/src/text_layout.rs     |  2 ++
crates/vim/src/editor_events.rs    | 15 ++++++++-------
crates/vim/src/insert.rs           |  2 +-
crates/vim/src/normal.rs           | 10 +++++-----
crates/vim/src/normal/change.rs    |  4 ++--
crates/vim/src/vim.rs              | 26 ++++++++------------------
crates/vim/src/vim_test_context.rs |  2 +-
crates/vim/src/visual.rs           |  8 ++++----
9 files changed, 36 insertions(+), 43 deletions(-)

Detailed changes

crates/editor/src/element.rs 🔗

@@ -1643,12 +1643,12 @@ impl PaintState {
         } else {
             0
         };
-        let column_overshoot = (0f32.max(x - line.width()) / layout.em_advance) as u32;
 
-        (
-            DisplayPoint::new(row, column),
-            DisplayPoint::new(row_overshoot, column_overshoot),
-        )
+        let point = snapshot.clip_point(DisplayPoint::new(row, column), Bias::Left);
+        let mut column_overshoot = (0f32.max(x - line.width()) / layout.em_advance) as u32;
+        column_overshoot = column_overshoot + column - point.column();
+
+        (point, DisplayPoint::new(row_overshoot, column_overshoot))
     }
 }
 

crates/gpui/src/text_layout.rs 🔗

@@ -238,6 +238,8 @@ impl Line {
         None
     }
 
+    // If round_to_closest, find the closest index to the given x position
+    // If !round_to_closest, find the largest index before the given x position
     pub fn index_for_x(&self, x: f32) -> Option<usize> {
         if x >= self.layout.width {
             None

crates/vim/src/editor_events.rs 🔗

@@ -34,12 +34,13 @@ fn editor_focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppCont
         }
 
         let editor = editor.read(cx);
-        if editor.selections.newest::<usize>(cx).is_empty() {
-            if editor.mode() != EditorMode::Full {
-                vim.switch_mode(Mode::Insert, cx);
-            }
-        } else {
-            vim.switch_mode(Mode::Visual { line: false }, cx);
+        let editor_mode = editor.mode();
+        let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
+
+        if editor_mode != EditorMode::Full {
+            vim.switch_mode(Mode::Insert, true, cx);
+        } else if !newest_selection_empty {
+            vim.switch_mode(Mode::Visual { line: false }, true, cx);
         }
     });
 }
@@ -69,7 +70,7 @@ fn editor_released(EditorReleased(editor): &EditorReleased, cx: &mut MutableAppC
 fn editor_local_selections_changed(newest_empty: bool, cx: &mut MutableAppContext) {
     Vim::update(cx, |vim, cx| {
         if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty {
-            vim.switch_mode(Mode::Visual { line: false }, cx)
+            vim.switch_mode(Mode::Visual { line: false }, false, cx)
         }
     })
 }

crates/vim/src/insert.rs 🔗

@@ -20,7 +20,7 @@ fn normal_before(_: &mut Workspace, _: &NormalBefore, cx: &mut ViewContext<Works
                 });
             });
         });
-        state.switch_mode(Mode::Normal, cx);
+        state.switch_mode(Mode::Normal, false, cx);
     })
 }
 

crates/vim/src/normal.rs 🔗

@@ -91,7 +91,7 @@ fn move_cursor(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
 
 fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext<Workspace>) {
     Vim::update(cx, |vim, cx| {
-        vim.switch_mode(Mode::Insert, cx);
+        vim.switch_mode(Mode::Insert, false, cx);
         vim.update_active_editor(cx, |editor, cx| {
             editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
                 s.move_cursors_with(|map, cursor, goal| {
@@ -108,7 +108,7 @@ fn insert_first_non_whitespace(
     cx: &mut ViewContext<Workspace>,
 ) {
     Vim::update(cx, |vim, cx| {
-        vim.switch_mode(Mode::Insert, cx);
+        vim.switch_mode(Mode::Insert, false, cx);
         vim.update_active_editor(cx, |editor, cx| {
             editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
                 s.move_cursors_with(|map, cursor, goal| {
@@ -121,7 +121,7 @@ fn insert_first_non_whitespace(
 
 fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewContext<Workspace>) {
     Vim::update(cx, |vim, cx| {
-        vim.switch_mode(Mode::Insert, cx);
+        vim.switch_mode(Mode::Insert, false, cx);
         vim.update_active_editor(cx, |editor, cx| {
             editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
                 s.move_cursors_with(|map, cursor, goal| {
@@ -134,7 +134,7 @@ fn insert_end_of_line(_: &mut Workspace, _: &InsertEndOfLine, cx: &mut ViewConte
 
 fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContext<Workspace>) {
     Vim::update(cx, |vim, cx| {
-        vim.switch_mode(Mode::Insert, cx);
+        vim.switch_mode(Mode::Insert, false, cx);
         vim.update_active_editor(cx, |editor, cx| {
             editor.transact(cx, |editor, cx| {
                 let (map, old_selections) = editor.selections.all_display(cx);
@@ -166,7 +166,7 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex
 
 fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext<Workspace>) {
     Vim::update(cx, |vim, cx| {
-        vim.switch_mode(Mode::Insert, cx);
+        vim.switch_mode(Mode::Insert, false, cx);
         vim.update_active_editor(cx, |editor, cx| {
             editor.transact(cx, |editor, cx| {
                 let (map, old_selections) = editor.selections.all_display(cx);

crates/vim/src/normal/change.rs 🔗

@@ -31,7 +31,7 @@ pub fn change_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
             editor.insert(&"", cx);
         });
     });
-    vim.switch_mode(Mode::Insert, cx)
+    vim.switch_mode(Mode::Insert, false, cx)
 }
 
 // From the docs https://vimhelp.org/change.txt.html#cw
@@ -70,7 +70,7 @@ fn change_word(
                 editor.insert(&"", cx);
             });
         });
-        vim.switch_mode(Mode::Insert, cx);
+        vim.switch_mode(Mode::Insert, false, cx);
     });
 }
 

crates/vim/src/vim.rs 🔗

@@ -36,7 +36,7 @@ pub fn init(cx: &mut MutableAppContext) {
 
     // Vim Actions
     cx.add_action(|_: &mut Workspace, &SwitchMode(mode): &SwitchMode, cx| {
-        Vim::update(cx, |vim, cx| vim.switch_mode(mode, cx))
+        Vim::update(cx, |vim, cx| vim.switch_mode(mode, false, cx))
     });
     cx.add_action(
         |_: &mut Workspace, &PushOperator(operator): &PushOperator, cx| {
@@ -62,7 +62,7 @@ pub fn init(cx: &mut MutableAppContext) {
         if vim.state.mode != Mode::Normal || vim.active_operator().is_some() {
             MutableAppContext::defer(cx, |cx| {
                 Vim::update(cx, |state, cx| {
-                    state.switch_mode(Mode::Normal, cx);
+                    state.switch_mode(Mode::Normal, false, cx);
                 });
             });
         } else {
@@ -115,37 +115,27 @@ impl Vim {
             .map(|ae| ae.update(cx, update))
     }
 
-    fn switch_mode(&mut self, mode: Mode, cx: &mut MutableAppContext) {
-        let previous_mode = self.state.mode;
+    fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut MutableAppContext) {
         self.state.mode = mode;
         self.state.operator_stack.clear();
 
         // Sync editor settings like clip mode
         self.sync_vim_settings(cx);
 
+        if leave_selections {
+            return;
+        }
+
         // Adjust selections
         for editor in self.editors.values() {
             if let Some(editor) = editor.upgrade(cx) {
                 editor.update(cx, |editor, cx| {
                     editor.change_selections(None, cx, |s| {
                         s.move_with(|map, selection| {
-                            // If empty selections
                             if self.state.empty_selections_only() {
                                 let new_head = map.clip_point(selection.head(), Bias::Left);
                                 selection.collapse_to(new_head, selection.goal)
                             } else {
-                                if matches!(mode, Mode::Visual { line: false })
-                                    && !matches!(previous_mode, Mode::Visual { .. })
-                                    && !selection.reversed
-                                    && !selection.is_empty()
-                                {
-                                    // Mode wasn't visual mode before, but is now. We need to move the end
-                                    // back by one character so that the region to be modifed stays the same
-                                    *selection.end.column_mut() =
-                                        selection.end.column().saturating_sub(1);
-                                    selection.end = map.clip_point(selection.end, Bias::Left);
-                                }
-
                                 selection.set_head(
                                     map.clip_point(selection.head(), Bias::Left),
                                     selection.goal,
@@ -183,7 +173,7 @@ impl Vim {
             self.enabled = enabled;
             self.state = Default::default();
             if enabled {
-                self.switch_mode(Mode::Normal, cx);
+                self.switch_mode(Mode::Normal, false, cx);
             }
             self.sync_vim_settings(cx);
         }

crates/vim/src/vim_test_context.rs 🔗

@@ -119,7 +119,7 @@ impl<'a> VimTestContext<'a> {
     pub fn set_state(&mut self, text: &str, mode: Mode) {
         self.cx.update(|cx| {
             Vim::update(cx, |vim, cx| {
-                vim.switch_mode(mode, cx);
+                vim.switch_mode(mode, false, cx);
             })
         });
         self.cx.set_state(text);

crates/vim/src/visual.rs 🔗

@@ -89,7 +89,7 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspac
                 s.select_anchors(new_selections);
             });
         });
-        vim.switch_mode(Mode::Insert, cx);
+        vim.switch_mode(Mode::Insert, false, cx);
     });
 }
 
@@ -130,7 +130,7 @@ pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspac
                 });
             });
         });
-        vim.switch_mode(Mode::Normal, cx);
+        vim.switch_mode(Mode::Normal, false, cx);
     });
 }
 
@@ -158,7 +158,7 @@ pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>)
                 });
             });
         });
-        vim.switch_mode(Mode::Normal, cx);
+        vim.switch_mode(Mode::Normal, false, cx);
     });
 }
 
@@ -266,7 +266,7 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext<Workspace>
                 }
             });
         });
-        vim.switch_mode(Mode::Normal, cx);
+        vim.switch_mode(Mode::Normal, false, cx);
     });
 }