From b196831a8da2fbd32043590b68337034aee02913 Mon Sep 17 00:00:00 2001 From: Mayank Verma Date: Tue, 6 Jan 2026 23:31:39 +0530 Subject: [PATCH] vim: Maintain end-of-line intent after $ with vertical motions (#45375) Closes #45340 Release Notes: - Fixed $ motion in vim mode to stay at end of the line when moving vertically --------- Co-authored-by: dino --- crates/vim/src/motion.rs | 58 ++++++++++++++++++- ...test_end_of_line_with_vertical_motion.json | 16 +++++ 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 crates/vim/test_data/test_end_of_line_with_vertical_motion.json diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index f2e629faf2dd4a5d1ff47a49278cdd022f75d8d4..67cf2b91dc9e0c5561a9c4f5d4d13179f64e24bd 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -10,7 +10,7 @@ use language::{CharKind, Point, Selection, SelectionGoal}; use multi_buffer::MultiBufferRow; use schemars::JsonSchema; use serde::Deserialize; -use std::ops::Range; +use std::{f64, ops::Range}; use workspace::searchable::Direction; use crate::{ @@ -1018,7 +1018,7 @@ impl Motion { ), EndOfLine { display_lines } => ( end_of_line(map, *display_lines, point, times), - SelectionGoal::None, + SelectionGoal::HorizontalPosition(f64::INFINITY), ), SentenceBackward => (sentence_backwards(map, point, times), SelectionGoal::None), SentenceForward => (sentence_forwards(map, point, times), SelectionGoal::None), @@ -3567,6 +3567,60 @@ mod test { cx.shared_state().await.assert_eq(" one \n twˇo \nthree"); } + #[gpui::test] + async fn test_end_of_line_with_vertical_motion(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + // test $ followed by k maintains end-of-line position + cx.set_shared_state(indoc! {" + The quick brown + fˇox + jumps over the + lazy dog + "}) + .await; + cx.simulate_shared_keystrokes("$ k").await; + cx.shared_state().await.assert_eq(indoc! {" + The quick browˇn + fox + jumps over the + lazy dog + "}); + cx.simulate_shared_keystrokes("j j").await; + cx.shared_state().await.assert_eq(indoc! {" + The quick brown + fox + jumps over thˇe + lazy dog + "}); + + // test horizontal movement resets the end-of-line behavior + cx.set_shared_state(indoc! {" + The quick brown fox + jumps over the + lazy ˇdog + "}) + .await; + cx.simulate_shared_keystrokes("$ k").await; + cx.shared_state().await.assert_eq(indoc! {" + The quick brown fox + jumps over thˇe + lazy dog + "}); + cx.simulate_shared_keystrokes("b b").await; + cx.shared_state().await.assert_eq(indoc! {" + The quick brown fox + jumps ˇover the + lazy dog + "}); + cx.simulate_shared_keystrokes("k").await; + cx.shared_state().await.assert_eq(indoc! {" + The quˇick brown fox + jumps over the + lazy dog + "}); + } + #[gpui::test] async fn test_window_top(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; diff --git a/crates/vim/test_data/test_end_of_line_with_vertical_motion.json b/crates/vim/test_data/test_end_of_line_with_vertical_motion.json new file mode 100644 index 0000000000000000000000000000000000000000..16487a5d4c25a32d096ae274ec023b7d9ebf8c55 --- /dev/null +++ b/crates/vim/test_data/test_end_of_line_with_vertical_motion.json @@ -0,0 +1,16 @@ +{"Put":{"state":"The quick brown\nfˇox\njumps over the\nlazy dog\n"}} +{"Key":"$"} +{"Key":"k"} +{"Get":{"state":"The quick browˇn\nfox\njumps over the\nlazy dog\n","mode":"Normal"}} +{"Key":"j"} +{"Key":"j"} +{"Get":{"state":"The quick brown\nfox\njumps over thˇe\nlazy dog\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox\njumps over the\nlazy ˇdog\n"}} +{"Key":"$"} +{"Key":"k"} +{"Get":{"state":"The quick brown fox\njumps over thˇe\nlazy dog\n","mode":"Normal"}} +{"Key":"b"} +{"Key":"b"} +{"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"}}