helix: Fix helix-paste mode in line mode (#38663)

Jakub Konka created

In particular,
* if the selection ends at the beginning of the next line, and the
current line under the cursor is empty, we paste at the selection's end.
* if however the current line under the cursor is empty, we need to move
to the beginning of the next line to avoid pasting above the end of
current selection

In addition, in line mode, we always move the cursor to the end of the
inserted text. Otherwise, while it looks fine visually,
inserting/appending ends up in the next logical line which is not
desirable.

Release Notes:

- N/A

Change summary

crates/vim/src/helix/paste.rs | 52 ++++++++++++++++++++++++++++++------
1 file changed, 43 insertions(+), 9 deletions(-)

Detailed changes

crates/vim/src/helix/paste.rs 🔗

@@ -84,13 +84,22 @@ impl Vim {
                     let display_point = if line_mode {
                         if action.before {
                             movement::line_beginning(&display_map, sel.start, false)
-                        } else if sel.end.column() == 0 {
+                        } else if sel.start.column() > 0
+                            && sel.end.column() == 0
+                            && sel.start != sel.end
+                        {
                             sel.end
                         } else {
-                            movement::right(
-                                &display_map,
-                                movement::line_end(&display_map, sel.end, false),
-                            )
+                            let point = movement::line_end(&display_map, sel.end, false);
+                            if sel.end.column() == 0 && point.column() > 0 {
+                                // If the selection ends at the beginning of the next line, and the current line
+                                // under the cursor is not empty, we paste at the selection's end.
+                                sel.end
+                            } else {
+                                // If however the current line under the cursor is empty, we need to move
+                                // to the beginning of the next line to avoid pasting above the end of current selection.
+                                movement::right(&display_map, point)
+                            }
                         }
                     } else if action.before {
                         sel.start
@@ -123,6 +132,12 @@ impl Vim {
                         let offset = anchor.to_offset(&snapshot);
                         if action.before {
                             offset.saturating_sub(len)..offset
+                        } else if line_mode {
+                            // In line mode, we always move the cursor to the end of the inserted text.
+                            // Otherwise, while it looks fine visually, inserting/appending ends up
+                            // in the next logical line which is not desirable.
+                            debug_assert!(len > 0);
+                            offset..(offset + len - 1)
                         } else {
                             offset..(offset + len)
                         }
@@ -386,8 +401,8 @@ mod test {
             indoc! {"
             The quick brown
             fox jumps over
-            «n
-            ˇ»the lazy dog."},
+            «nˇ»
+            the lazy dog."},
             Mode::HelixNormal,
         );
 
@@ -405,8 +420,27 @@ mod test {
             indoc! {"
             The quick brown
             fox jumps over
-            «n
-            ˇ»the lazy dog."},
+            «nˇ»
+            the lazy dog."},
+            Mode::HelixNormal,
+        );
+
+        cx.set_state(
+            indoc! {"
+
+            The quick brown
+            fox jumps overˇ
+            the lazy dog."},
+            Mode::HelixNormal,
+        );
+        cx.simulate_keystrokes("x y up up p");
+        cx.assert_state(
+            indoc! {"
+
+            «fox jumps overˇ»
+            The quick brown
+            fox jumps over
+            the lazy dog."},
             Mode::HelixNormal,
         );
     }