From f0aeab7d00cf130fb146ce874b13723904b67354 Mon Sep 17 00:00:00 2001 From: Mike Lloyd <49411532+mike-lloyd03@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:03:27 -0800 Subject: [PATCH] Add surround aliases (#20104) Closes #19417 Release Notes: - vim : Added `r` and `a` as aliases for `[` and `<` text objects (copying vim-surround). - vim: (breaking change) rebound the function argument text object to `g`. - vim: Fixed surrounds to allow `b`/`B`/`r`/`a` anywhere you could use `(`, `{`, `[`, `<`. --- - vim: Added `b`, `B`, `r`, `s`, `a` as aliases for `()`, `{}`, `[]`, `<>` in vim surround mode. - Adds a new `surround_alias` function where aliases are defined. - This function is used in `find_surround_pairs` to substitute the chosen text with the alias - The keymap is also modified to add support for Square and Angle brackets when changing surrounds. These two were added to follow the example of Tim Pope's ubiquitous `vim-surround` plugin. - I had to overwrite the `vim::Argument` keybind in order to do this. I moved it to use the `g` modifier. I realize this is a breaking change and will happily move the `vim::AngleBracket` keymap to a different letter if you'd like to avoid this. I'm just trying to keep with convention. Ideally, Users would be able to define surround aliases themselves in the config file but that's a much bigger task than I'm able to do right now. - I also added tests for the new aliases. Thanks for making such a clean and organized codebase. I was able to find the relevant section of code rather quickly thanks to this. --- assets/keymaps/vim.json | 4 +- crates/vim/src/object.rs | 18 +-- crates/vim/src/surrounds.rs | 250 +++++++++++++++++++++++++++++++++++- 3 files changed, 259 insertions(+), 13 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 486292f20e4b188e9de393caf308f56cdc1b9b62..9929c6f90755c02c19485a69a82bba9250cb87c6 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -364,12 +364,14 @@ "b": "vim::Parentheses", "[": "vim::SquareBrackets", "]": "vim::SquareBrackets", + "r": "vim::SquareBrackets", "{": "vim::CurlyBrackets", "}": "vim::CurlyBrackets", "shift-b": "vim::CurlyBrackets", "<": "vim::AngleBrackets", ">": "vim::AngleBrackets", - "a": "vim::Argument" + "a": "vim::AngleBrackets", + "g": "vim::Argument" } }, { diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 9f8e8366a729c0705a5638675d4b1b9d74719d32..c73cee836e3f5ae1b88f67c62180dacc725131c8 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -1402,7 +1402,7 @@ mod test { // Generic arguments cx.set_state("fn boop() {}", Mode::Normal); - cx.simulate_keystrokes("v i a"); + cx.simulate_keystrokes("v i g"); cx.assert_state("fn boop<«A: Debugˇ», B>() {}", Mode::Visual); // Function arguments @@ -1410,11 +1410,11 @@ mod test { "fn boop(ˇarg_a: (Tuple, Of, Types), arg_b: String) {}", Mode::Normal, ); - cx.simulate_keystrokes("d a a"); + cx.simulate_keystrokes("d a g"); cx.assert_state("fn boop(ˇarg_b: String) {}", Mode::Normal); cx.set_state("std::namespace::test(\"strinˇg\", a.b.c())", Mode::Normal); - cx.simulate_keystrokes("v a a"); + cx.simulate_keystrokes("v a g"); cx.assert_state("std::namespace::test(«\"string\", ˇ»a.b.c())", Mode::Visual); // Tuple, vec, and array arguments @@ -1422,34 +1422,34 @@ mod test { "fn boop(arg_a: (Tuple, Ofˇ, Types), arg_b: String) {}", Mode::Normal, ); - cx.simulate_keystrokes("c i a"); + cx.simulate_keystrokes("c i g"); cx.assert_state( "fn boop(arg_a: (Tuple, ˇ, Types), arg_b: String) {}", Mode::Insert, ); cx.set_state("let a = (test::call(), 'p', my_macro!{ˇ});", Mode::Normal); - cx.simulate_keystrokes("c a a"); + cx.simulate_keystrokes("c a g"); cx.assert_state("let a = (test::call(), 'p'ˇ);", Mode::Insert); cx.set_state("let a = [test::call(ˇ), 300];", Mode::Normal); - cx.simulate_keystrokes("c i a"); + cx.simulate_keystrokes("c i g"); cx.assert_state("let a = [ˇ, 300];", Mode::Insert); cx.set_state( "let a = vec![Vec::new(), vecˇ![test::call(), 300]];", Mode::Normal, ); - cx.simulate_keystrokes("c a a"); + cx.simulate_keystrokes("c a g"); cx.assert_state("let a = vec![Vec::new()ˇ];", Mode::Insert); // Cursor immediately before / after brackets cx.set_state("let a = [test::call(first_arg)ˇ]", Mode::Normal); - cx.simulate_keystrokes("v i a"); + cx.simulate_keystrokes("v i g"); cx.assert_state("let a = [«test::call(first_arg)ˇ»]", Mode::Visual); cx.set_state("let a = [test::callˇ(first_arg)]", Mode::Normal); - cx.simulate_keystrokes("v i a"); + cx.simulate_keystrokes("v i g"); cx.assert_state("let a = [«test::call(first_arg)ˇ»]", Mode::Visual); } diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index 81025103fb7ca423127474f32f1f371545dbbfa1..781485e4cceee54bccb0b4939ee7af19256094ad 100644 --- a/crates/vim/src/surrounds.rs +++ b/crates/vim/src/surrounds.rs @@ -52,7 +52,7 @@ impl Vim { newline: false, }, }; - let surround = pair.end != *text; + let surround = pair.end != surround_alias((*text).as_ref()); let (display_map, display_selections) = editor.selections.all_adjusted_display(cx); let mut edits = Vec::new(); let mut anchors = Vec::new(); @@ -245,7 +245,7 @@ impl Vim { newline: false, }, }; - let surround = pair.end != *text; + let surround = pair.end != surround_alias((*text).as_ref()); let (display_map, selections) = editor.selections.all_adjusted_display(cx); let mut edits = Vec::new(); let mut anchors = Vec::new(); @@ -393,7 +393,19 @@ impl Vim { } fn find_surround_pair<'a>(pairs: &'a [BracketPair], ch: &str) -> Option<&'a BracketPair> { - pairs.iter().find(|pair| pair.start == ch || pair.end == ch) + pairs + .iter() + .find(|pair| pair.start == surround_alias(ch) || pair.end == surround_alias(ch)) +} + +fn surround_alias(ch: &str) -> &str { + match ch { + "b" => ")", + "B" => "}", + "a" => ">", + "r" => "]", + _ => ch, + } } fn all_support_surround_pair() -> Vec { @@ -1171,4 +1183,236 @@ mod test { Mode::Normal, ); } + + #[gpui::test] + async fn test_surround_aliases(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + // add aliases + cx.set_state( + indoc! {" + The quˇick brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("y s i w b"); + cx.assert_state( + indoc! {" + The ˇ(quick) brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + cx.set_state( + indoc! {" + The quˇick brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("y s i w B"); + cx.assert_state( + indoc! {" + The ˇ{quick} brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + cx.set_state( + indoc! {" + The quˇick brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("y s i w a"); + cx.assert_state( + indoc! {" + The ˇ brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + cx.set_state( + indoc! {" + The quˇick brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("y s i w r"); + cx.assert_state( + indoc! {" + The ˇ[quick] brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + // change aliases + cx.set_state( + indoc! {" + The {quˇick} brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("c s { b"); + cx.assert_state( + indoc! {" + The ˇ(quick) brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + cx.set_state( + indoc! {" + The (quˇick) brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("c s ( B"); + cx.assert_state( + indoc! {" + The ˇ{quick} brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + cx.set_state( + indoc! {" + The (quˇick) brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("c s ( a"); + cx.assert_state( + indoc! {" + The ˇ brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + cx.set_state( + indoc! {" + The brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("c s < b"); + cx.assert_state( + indoc! {" + The ˇ(quick) brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + cx.set_state( + indoc! {" + The (quˇick) brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("c s ( r"); + cx.assert_state( + indoc! {" + The ˇ[quick] brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + cx.set_state( + indoc! {" + The [quˇick] brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("c s [ b"); + cx.assert_state( + indoc! {" + The ˇ(quick) brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + // delete alias + cx.set_state( + indoc! {" + The {quˇick} brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("d s B"); + cx.assert_state( + indoc! {" + The ˇquick brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + cx.set_state( + indoc! {" + The (quˇick) brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("d s b"); + cx.assert_state( + indoc! {" + The ˇquick brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + cx.set_state( + indoc! {" + The [quˇick] brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("d s r"); + cx.assert_state( + indoc! {" + The ˇquick brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + + cx.set_state( + indoc! {" + The brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + cx.simulate_keystrokes("d s a"); + cx.assert_state( + indoc! {" + The ˇquick brown + fox jumps over + the lazy dog."}, + Mode::Normal, + ); + } }