diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 233c9fa7e4468142c3e5a31b730bb4d80b83a907..34228530d2d398348d57d71ae41654dacb479712 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -414,8 +414,9 @@ } }, { - "context": "vim_mode == helix_normal && !menu", + "context": "VimControl && vim_mode == helix_normal && !menu", "bindings": { + "escape": "vim::SwitchToHelixNormalMode", "i": "vim::HelixInsert", "a": "vim::HelixAppend", "ctrl-[": "editor::Cancel" diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index eb0749794adb321d8ce19f8ad5adcf67b9a41bba..67c99ff6aea249692bddc38d3681be5c491a7437 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -263,6 +263,31 @@ impl Vim { cx: &mut Context, ) { match motion { + Motion::EndOfLine { .. } => { + // In Helix mode, EndOfLine should position cursor ON the last character, + // not after it. We therefore need special handling for it. + self.update_editor(cx, |_, editor, cx| { + let text_layout_details = editor.text_layout_details(window); + editor.change_selections(Default::default(), window, cx, |s| { + s.move_with(|map, selection| { + let goal = selection.goal; + let cursor = if selection.is_empty() || selection.reversed { + selection.head() + } else { + movement::left(map, selection.head()) + }; + + let (point, _goal) = motion + .move_point(map, cursor, goal, times, &text_layout_details) + .unwrap_or((cursor, goal)); + + // Move left by one character to position on the last character + let adjusted_point = movement::saturating_left(map, point); + selection.collapse_to(adjusted_point, SelectionGoal::None) + }) + }); + }); + } Motion::NextWordStart { ignore_punctuation } => { self.helix_find_range_forward(times, window, cx, |left, right, classifier| { let left_kind = classifier.kind_with(left, ignore_punctuation); @@ -1493,4 +1518,59 @@ mod test { Mode::Insert, ); } + + #[gpui::test] + async fn test_g_l_end_of_line(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + cx.enable_helix(); + + // Test g l moves to last character, not after it + cx.set_state("hello ˇworld!", Mode::HelixNormal); + cx.simulate_keystrokes("g l"); + cx.assert_state("hello worldˇ!", Mode::HelixNormal); + + // Test with Chinese characters, test if work with UTF-8? + cx.set_state("ˇ你好世界", Mode::HelixNormal); + cx.simulate_keystrokes("g l"); + cx.assert_state("你好世ˇ界", Mode::HelixNormal); + + // Test with end of line + cx.set_state("endˇ", Mode::HelixNormal); + cx.simulate_keystrokes("g l"); + cx.assert_state("enˇd", Mode::HelixNormal); + + // Test with empty line + cx.set_state( + indoc! {" + hello + ˇ + world"}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("g l"); + cx.assert_state( + indoc! {" + hello + ˇ + world"}, + Mode::HelixNormal, + ); + + // Test with multiple lines + cx.set_state( + indoc! {" + ˇfirst line + second line + third line"}, + Mode::HelixNormal, + ); + cx.simulate_keystrokes("g l"); + cx.assert_state( + indoc! {" + first linˇe + second line + third line"}, + Mode::HelixNormal, + ); + } }