diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index e99eeed273..fe0613eac3 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -247,7 +247,8 @@ "context": "VimControl && VimCount", "bindings": { "0": ["vim::Number", 0], - ":": "vim::CountCommand" + ":": "vim::CountCommand", + "%": "vim::GoToPercentage" } }, { diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 55614a10bb..a0ab12b2bb 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -74,6 +74,7 @@ pub enum Motion { StartOfDocument, EndOfDocument, Matching, + GoToPercentage, UnmatchedForward { char: char, }, @@ -281,6 +282,7 @@ actions!( StartOfDocument, EndOfDocument, Matching, + GoToPercentage, NextLineStart, PreviousLineStart, StartOfLineDownward, @@ -402,6 +404,9 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, |vim, _: &Matching, window, cx| { vim.motion(Motion::Matching, window, cx) }); + Vim::action(editor, cx, |vim, _: &GoToPercentage, window, cx| { + vim.motion(Motion::GoToPercentage, window, cx) + }); Vim::action( editor, cx, @@ -643,6 +648,7 @@ impl Motion { | PreviousMethodEnd | NextComment | PreviousComment + | GoToPercentage | Jump { line: true, .. } => true, EndOfLine { .. } | Matching @@ -701,6 +707,7 @@ impl Motion { | StartOfLineDownward | EndOfLineDownward | GoToColumn + | GoToPercentage | NextWordStart { .. } | NextWordEnd { .. } | PreviousWordStart { .. } @@ -745,6 +752,7 @@ impl Motion { | EndOfLine { .. } | EndOfLineDownward | Matching + | GoToPercentage | UnmatchedForward { .. } | UnmatchedBackward { .. } | FindForward { .. } @@ -886,6 +894,7 @@ impl Motion { SelectionGoal::None, ), Matching => (matching(map, point), SelectionGoal::None), + GoToPercentage => (go_to_percentage(map, point, times), SelectionGoal::None), UnmatchedForward { char } => ( unmatched_forward(map, point, *char, times), SelectionGoal::None, @@ -2194,6 +2203,22 @@ fn matching(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint } } +// Go to {count} percentage in the file, on the first +// non-blank in the line linewise. To compute the new +// line number this formula is used: +// ({count} * number-of-lines + 99) / 100 +// +// https://neovim.io/doc/user/motion.html#N%25 +fn go_to_percentage(map: &DisplaySnapshot, point: DisplayPoint, count: usize) -> DisplayPoint { + let total_lines = map.buffer_snapshot.max_point().row + 1; + let target_line = (count * total_lines as usize + 99) / 100; + let target_point = DisplayPoint::new( + DisplayRow(target_line.saturating_sub(1) as u32), + point.column(), + ); + map.clip_point(target_point, Bias::Left) +} + fn unmatched_forward( map: &DisplaySnapshot, mut display_point: DisplayPoint, @@ -3470,4 +3495,103 @@ mod test { Mode::Normal, ); } + + #[gpui::test] + async fn test_go_to_percentage(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + // Normal mode + cx.set_shared_state(indoc! {" + The ˇquick brown + fox jumps over + the lazy dog + The quick brown + fox jumps over + the lazy dog + The quick brown + fox jumps over + the lazy dog"}) + .await; + cx.simulate_shared_keystrokes("2 0 %").await; + cx.shared_state().await.assert_eq(indoc! {" + The quick brown + fox ˇjumps over + the lazy dog + The quick brown + fox jumps over + the lazy dog + The quick brown + fox jumps over + the lazy dog"}); + + cx.simulate_shared_keystrokes("2 5 %").await; + cx.shared_state().await.assert_eq(indoc! {" + The quick brown + fox jumps over + the ˇlazy dog + The quick brown + fox jumps over + the lazy dog + The quick brown + fox jumps over + the lazy dog"}); + + cx.simulate_shared_keystrokes("7 5 %").await; + cx.shared_state().await.assert_eq(indoc! {" + The quick brown + fox jumps over + the lazy dog + The quick brown + fox jumps over + the lazy dog + The ˇquick brown + fox jumps over + the lazy dog"}); + + // Visual mode + cx.set_shared_state(indoc! {" + The ˇquick brown + fox jumps over + the lazy dog + The quick brown + fox jumps over + the lazy dog + The quick brown + fox jumps over + the lazy dog"}) + .await; + cx.simulate_shared_keystrokes("v 5 0 %").await; + cx.shared_state().await.assert_eq(indoc! {" + The «quick brown + fox jumps over + the lazy dog + The quick brown + fox jˇ»umps over + the lazy dog + The quick brown + fox jumps over + the lazy dog"}); + + cx.set_shared_state(indoc! {" + The ˇquick brown + fox jumps over + the lazy dog + The quick brown + fox jumps over + the lazy dog + The quick brown + fox jumps over + the lazy dog"}) + .await; + cx.simulate_shared_keystrokes("v 1 0 0 %").await; + cx.shared_state().await.assert_eq(indoc! {" + The «quick brown + fox jumps over + the lazy dog + The quick brown + fox jumps over + the lazy dog + The quick brown + fox jumps over + the lˇ»azy dog"}); + } } diff --git a/crates/vim/test_data/test_go_to_percentage.json b/crates/vim/test_data/test_go_to_percentage.json new file mode 100644 index 0000000000..d7efd170f2 --- /dev/null +++ b/crates/vim/test_data/test_go_to_percentage.json @@ -0,0 +1,26 @@ +{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lazy dog"}} +{"Key":"2"} +{"Key":"0"} +{"Key":"%"} +{"Get":{"state":"The quick brown\nfox ˇjumps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}} +{"Key":"2"} +{"Key":"5"} +{"Key":"%"} +{"Get":{"state":"The quick brown\nfox jumps over\nthe ˇlazy dog\nThe quick brown\nfox jumps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}} +{"Key":"7"} +{"Key":"5"} +{"Key":"%"} +{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lazy dog\nThe ˇquick brown\nfox jumps over\nthe lazy dog","mode":"Normal"}} +{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lazy dog"}} +{"Key":"v"} +{"Key":"5"} +{"Key":"0"} +{"Key":"%"} +{"Get":{"state":"The «quick brown\nfox jumps over\nthe lazy dog\nThe quick brown\nfox jˇ»umps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lazy dog","mode":"Visual"}} +{"Put":{"state":"The ˇquick brown\nfox jumps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lazy dog"}} +{"Key":"v"} +{"Key":"1"} +{"Key":"0"} +{"Key":"0"} +{"Key":"%"} +{"Get":{"state":"The «quick brown\nfox jumps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lazy dog\nThe quick brown\nfox jumps over\nthe lˇ»azy dog","mode":"Visual"}}