vim: Make end of line infallible (#48867)

Dino created

When using `$` to move to the end of line (`vim::EndOfLine`), the
`vim::motion::Motion.move_point` method checks whether the new point,
that is, the point after the motion is applied is different from the
point that was passed as a method argument. If the point is not
different, the point and selection goals are only updated if
`vim::motion::Motion.infallible` returns true for the motion in
question.

In short, this means that, if the cursor was already at the end of the
line, and it got there using `vim::Right`, for example, the selection
goal wouldn't actually be set to
`SelectionGoal::HorizontalPosition(f64::INFINITY)`, so when the cursor
was moved to a shorter line, it wouldn't be set at the end of that line,
even though `$` had been used.

This commit updates `vim::motion::Motion.infallible` to ensure that, for
`vim::motion::Motion::EndOfLine`, it returns `true`, so that the
selection goal is always updated, regardless of whether the cursor is
already at the end of the line.

Closes #48855 

- [X] Tests or screenshots needed?
- [X] Code Reviewed
- [X] Manual QA

Release Notes:

- vim: Fixed `$` not sticking to end-of-line on vertical motions
(`j`/`k`) when the cursor was already at the end of the line via `l` or
arrow keys

Change summary

crates/vim/src/motion.rs                                        | 26 ++
crates/vim/test_data/test_end_of_line_with_vertical_motion.json |  6 
2 files changed, 30 insertions(+), 2 deletions(-)

Detailed changes

crates/vim/src/motion.rs 🔗

@@ -899,10 +899,9 @@ impl Motion {
     pub fn infallible(&self) -> bool {
         use Motion::*;
         match self {
-            StartOfDocument | EndOfDocument | CurrentLine => true,
+            StartOfDocument | EndOfDocument | CurrentLine | EndOfLine { .. } => true,
             Down { .. }
             | Up { .. }
-            | EndOfLine { .. }
             | MiddleOfLine { .. }
             | Matching { .. }
             | UnmatchedForward { .. }
@@ -3901,6 +3900,29 @@ mod test {
             jumps over the
             lazy dog
             "});
+
+        // Test that, when the cursor is moved to the end of the line using `l`,
+        // if `$` is used, the cursor stays at the end of the line when moving
+        // to a longer line, ensuring that the selection goal was correctly
+        // updated.
+        cx.set_shared_state(indoc! {"
+            The quick brown fox
+            jumps over the
+            lazy dˇog
+            "})
+            .await;
+        cx.simulate_shared_keystrokes("l").await;
+        cx.shared_state().await.assert_eq(indoc! {"
+            The quick brown fox
+            jumps over the
+            lazy doˇg
+            "});
+        cx.simulate_shared_keystrokes("$ k").await;
+        cx.shared_state().await.assert_eq(indoc! {"
+            The quick brown fox
+            jumps over thˇe
+            lazy dog
+            "});
     }
 
     #[gpui::test]

crates/vim/test_data/test_end_of_line_with_vertical_motion.json 🔗

@@ -14,3 +14,9 @@
 {"Get":{"state":"The quick brown fox\njumps ˇover the\nlazy dog\n","mode":"Normal"}}
 {"Key":"k"}
 {"Get":{"state":"The quˇick brown fox\njumps over the\nlazy dog\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown fox\njumps over the\nlazy dˇog\n"}}
+{"Key":"l"}
+{"Get":{"state":"The quick brown fox\njumps over the\nlazy doˇg\n","mode":"Normal"}}
+{"Key":"$"}
+{"Key":"k"}
+{"Get":{"state":"The quick brown fox\njumps over thˇe\nlazy dog\n","mode":"Normal"}}