Fixup paste locations

Keith Simmons created

Change summary

crates/editor/src/editor.rs                | 32 +++++++++++--------
crates/editor/src/selections_collection.rs |  7 ----
crates/vim/src/normal.rs                   | 39 ++++++++++++++++++++++++
crates/vim/src/visual.rs                   | 30 ++++++++++--------
4 files changed, 75 insertions(+), 33 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -1542,12 +1542,10 @@ impl Editor {
         }
 
         self.change_selections(Some(Autoscroll::Fit), cx, |s| {
-            if add {
-                if click_count > 1 {
-                    s.delete(newest_selection.id);
-                }
-            } else {
+            if !add {
                 s.clear_disjoint();
+            } else if click_count > 1 {
+                s.delete(newest_selection.id)
             }
 
             s.set_pending_range(start..end, mode);
@@ -3283,8 +3281,9 @@ impl Editor {
         self.transact(cx, |this, cx| {
             let edits = this.change_selections(Some(Autoscroll::Fit), cx, |s| {
                 let mut edits: Vec<(Range<usize>, String)> = Default::default();
+                let line_mode = s.line_mode;
                 s.move_with(|display_map, selection| {
-                    if !selection.is_empty() {
+                    if !selection.is_empty() || line_mode {
                         return;
                     }
 
@@ -3422,6 +3421,7 @@ impl Editor {
                         let snapshot = buffer.read(cx);
                         let mut start_offset = 0;
                         let mut edits = Vec::new();
+                        let line_mode = this.selections.line_mode;
                         for (ix, selection) in old_selections.iter().enumerate() {
                             let to_insert;
                             let entire_line;
@@ -3439,7 +3439,7 @@ impl Editor {
                             // clipboard text was written, then the entire line containing the
                             // selection was copied. If this selection is also currently empty,
                             // then paste the line before the current line of the buffer.
-                            let range = if selection.is_empty() && entire_line {
+                            let range = if selection.is_empty() && !line_mode && entire_line {
                                 let column = selection.start.to_point(&snapshot).column as usize;
                                 let line_start = selection.start - column;
                                 line_start..line_start
@@ -3494,8 +3494,9 @@ impl Editor {
 
     pub fn move_left(&mut self, _: &MoveLeft, cx: &mut ViewContext<Self>) {
         self.change_selections(Some(Autoscroll::Fit), cx, |s| {
+            let line_mode = s.line_mode;
             s.move_with(|map, selection| {
-                let cursor = if selection.is_empty() {
+                let cursor = if selection.is_empty() && !line_mode {
                     movement::left(map, selection.start)
                 } else {
                     selection.start
@@ -3513,8 +3514,9 @@ impl Editor {
 
     pub fn move_right(&mut self, _: &MoveRight, cx: &mut ViewContext<Self>) {
         self.change_selections(Some(Autoscroll::Fit), cx, |s| {
+            let line_mode = s.line_mode;
             s.move_with(|map, selection| {
-                let cursor = if selection.is_empty() {
+                let cursor = if selection.is_empty() && !line_mode {
                     movement::right(map, selection.end)
                 } else {
                     selection.end
@@ -3547,8 +3549,9 @@ impl Editor {
         }
 
         self.change_selections(Some(Autoscroll::Fit), cx, |s| {
+            let line_mode = s.line_mode;
             s.move_with(|map, selection| {
-                if !selection.is_empty() {
+                if !selection.is_empty() && !line_mode {
                     selection.goal = SelectionGoal::None;
                 }
                 let (cursor, goal) = movement::up(&map, selection.start, selection.goal, false);
@@ -3578,8 +3581,9 @@ impl Editor {
         }
 
         self.change_selections(Some(Autoscroll::Fit), cx, |s| {
+            let line_mode = s.line_mode;
             s.move_with(|map, selection| {
-                if !selection.is_empty() {
+                if !selection.is_empty() && !line_mode {
                     selection.goal = SelectionGoal::None;
                 }
                 let (cursor, goal) = movement::down(&map, selection.end, selection.goal, false);
@@ -3680,8 +3684,9 @@ impl Editor {
     ) {
         self.transact(cx, |this, cx| {
             this.change_selections(Some(Autoscroll::Fit), cx, |s| {
+                let line_mode = s.line_mode;
                 s.move_with(|map, selection| {
-                    if selection.is_empty() {
+                    if selection.is_empty() && !line_mode {
                         let cursor = movement::previous_subword_start(map, selection.head());
                         selection.set_head(cursor, SelectionGoal::None);
                     }
@@ -3734,8 +3739,9 @@ impl Editor {
     pub fn delete_to_next_word_end(&mut self, _: &DeleteToNextWordEnd, cx: &mut ViewContext<Self>) {
         self.transact(cx, |this, cx| {
             this.change_selections(Some(Autoscroll::Fit), cx, |s| {
+                let line_mode = s.line_mode;
                 s.move_with(|map, selection| {
-                    if selection.is_empty() {
+                    if selection.is_empty() && !line_mode {
                         let cursor = movement::next_word_end(map, selection.head());
                         selection.set_head(cursor, SelectionGoal::None);
                     }

crates/editor/src/selections_collection.rs 🔗

@@ -22,13 +22,6 @@ pub struct PendingSelection {
     pub mode: SelectMode,
 }
 
-#[derive(Clone)]
-pub enum LineMode {
-    None,
-    WithNewline,
-    WithoutNewline,
-}
-
 #[derive(Clone)]
 pub struct SelectionsCollection {
     display_map: ModelHandle<DisplayMap>,

crates/vim/src/normal.rs 🔗

@@ -194,6 +194,7 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex
     });
 }
 
+// Supports non empty selections so it can be bound and called from visual mode
 fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
     Vim::update(cx, |vim, cx| {
         vim.update_active_editor(cx, |editor, cx| {
@@ -256,6 +257,23 @@ fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
                                     new_selections.push(selection.map(|_| selection_point.clone()));
                                     point..point
                                 } else {
+                                    let mut selection = selection.clone();
+                                    if !selection.reversed {
+                                        let mut adjusted = selection.end;
+                                        // Head is at the end of the selection. Adjust the end position to
+                                        // to include the character under the cursor.
+                                        *adjusted.column_mut() = adjusted.column() + 1;
+                                        adjusted = display_map.clip_point(adjusted, Bias::Right);
+                                        // If the selection is empty, move both the start and end forward one
+                                        // character
+                                        if selection.is_empty() {
+                                            selection.start = adjusted;
+                                            selection.end = adjusted;
+                                        } else {
+                                            selection.end = adjusted;
+                                        }
+                                    }
+
                                     let range = selection.map(|p| p.to_point(&display_map)).range();
                                     new_selections.push(selection.map(|_| range.start.clone()));
                                     range
@@ -1141,5 +1159,26 @@ mod test {
             The quick brown
             the lazy dog
             |fox jumps over"});
+
+        cx.set_state(
+            indoc! {"
+                The quick brown
+                fox [jump}s over
+                the lazy dog"},
+            Mode::Normal,
+        );
+        cx.simulate_keystroke("y");
+        cx.set_state(
+            indoc! {"
+                The quick brown
+                fox jump|s over
+                the lazy dog"},
+            Mode::Normal,
+        );
+        cx.simulate_keystroke("p");
+        cx.assert_editor_state(indoc! {"
+            The quick brown
+            fox jumps|jumps over
+            the lazy dog"});
     }
 }

crates/vim/src/visual.rs 🔗

@@ -6,7 +6,7 @@ use workspace::Workspace;
 
 use crate::{motion::Motion, state::Mode, utils::copy_selections_content, Vim};
 
-actions!(vim, [VisualDelete, VisualChange, VisualYank,]);
+actions!(vim, [VisualDelete, VisualChange, VisualYank]);
 
 pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(change);
@@ -55,7 +55,7 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspac
                         // Head is at the end of the selection. Adjust the end position to
                         // to include the character under the cursor.
                         *selection.end.column_mut() = selection.end.column() + 1;
-                        selection.end = map.clip_point(selection.end, Bias::Left);
+                        selection.end = map.clip_point(selection.end, Bias::Right);
                     }
 
                     if line_mode {
@@ -77,6 +77,7 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspac
                         edits.push((range, ""));
                         new_selections.push(selection.map(|_| anchor.clone()));
                     }
+                    selection.goal = SelectionGoal::None;
                 });
             });
             copy_selections_content(editor, editor.selections.line_mode, cx);
@@ -106,6 +107,7 @@ pub fn delete(_: &mut Workspace, _: &VisualDelete, cx: &mut ViewContext<Workspac
                         *selection.end.column_mut() = selection.end.column() + 1;
                         selection.end = map.clip_point(selection.end, Bias::Right);
                     }
+                    selection.goal = SelectionGoal::None;
                 });
             });
             copy_selections_content(editor, line_mode, cx);
@@ -134,16 +136,18 @@ pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>)
         vim.update_active_editor(cx, |editor, cx| {
             editor.set_clip_at_line_ends(false, cx);
             let line_mode = editor.selections.line_mode;
-            editor.change_selections(None, cx, |s| {
-                s.move_with(|map, selection| {
-                    if !line_mode && !selection.reversed {
-                        // Head is at the end of the selection. Adjust the end position to
-                        // to include the character under the cursor.
-                        *selection.end.column_mut() = selection.end.column() + 1;
-                        selection.end = map.clip_point(selection.end, Bias::Left);
-                    }
+            if !editor.selections.line_mode {
+                editor.change_selections(None, cx, |s| {
+                    s.move_with(|map, selection| {
+                        if !selection.reversed {
+                            // Head is at the end of the selection. Adjust the end position to
+                            // to include the character under the cursor.
+                            *selection.end.column_mut() = selection.end.column() + 1;
+                            selection.end = map.clip_point(selection.end, Bias::Right);
+                        }
+                    });
                 });
-            });
+            }
             copy_selections_content(editor, line_mode, cx);
             editor.change_selections(None, cx, |s| {
                 s.move_with(|_, selection| {
@@ -251,8 +255,8 @@ mod test {
         cx.simulate_keystrokes(["j", "p"]);
         cx.assert_editor_state(indoc! {"
             The ver
-            the lazy d|quick brown
-            fox jumps oog"});
+            the l|quick brown
+            fox jumps oazy dog"});
 
         cx.assert(
             indoc! {"