vim: Fix change surrounding quotes with whitespace within (#37321)

Dino created

This commit fixes a bug with Zed's vim mode surrounds plugin when
dealing with replacing pairs with quote and the contents between the
pairs had some whitespace within them.

For example, with the following string:

```
' str '
```

If one was to use the `cs'"` command, to replace single quotes with
double quotes, the result would actually be:

```
"str"
```

As the whitespace before and after the closing character was removed.

This happens because of the way the plugin decides whether to add or
remove whitespace after and before the opening and closing characters,
repsectively. For example, using `cs{[` yields a different result from
using `cs{]`, the former adds a space while the latter does not.

However, since for quotes the opening and closing character is exactly
the same, this behavior is not possible, so this commit updates the code
in `vim::surrounds::Vim.change_surrounds` so that it never adds or
removes whitespace when dealing with any type of quotes.

Closes #12247 

Release Notes:

- Fixed whitespace handling when changing surrounding pairs to quotes in
vim mode

Change summary

crates/vim/src/surrounds.rs | 43 ++++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+), 1 deletion(-)

Detailed changes

crates/vim/src/surrounds.rs 🔗

@@ -240,7 +240,24 @@ impl Vim {
                             newline: false,
                         },
                     };
-                    let surround = pair.end != surround_alias((*text).as_ref());
+
+                    // Determines whether space should be added/removed after
+                    // and before the surround pairs.
+                    // For example, using `cs{[` will add a space before and
+                    // after the pair, while using `cs{]` will not, notice the
+                    // use of the closing bracket instead of the opening bracket
+                    // on the target object.
+                    // In the case of quotes, the opening and closing is the
+                    // same, so no space will ever be added or removed.
+                    let surround = match target {
+                        Object::Quotes
+                        | Object::BackQuotes
+                        | Object::AnyQuotes
+                        | Object::MiniQuotes
+                        | Object::DoubleQuotes => true,
+                        _ => 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();
@@ -1128,6 +1145,30 @@ mod test {
             ];"},
             Mode::Normal,
         );
+
+        // test change quotes.
+        cx.set_state(indoc! {"'  ˇstr  '"}, Mode::Normal);
+        cx.simulate_keystrokes("c s ' \"");
+        cx.assert_state(indoc! {"ˇ\"  str  \""}, Mode::Normal);
+
+        // test multi cursor change quotes
+        cx.set_state(
+            indoc! {"
+            '  ˇstr  '
+            some example text here
+            ˇ'  str  '
+        "},
+            Mode::Normal,
+        );
+        cx.simulate_keystrokes("c s ' \"");
+        cx.assert_state(
+            indoc! {"
+            ˇ\"  str  \"
+            some example text here
+            ˇ\"  str  \"
+        "},
+            Mode::Normal,
+        );
     }
 
     #[gpui::test]