editor: Fix auto-closing quotes after word character (#26803)

Smit Barmase created

Closes #14349

When typing quotes immediately after a word character, it resulted in
auto-closing the quote.

```js
const thing = this is text^;
```

Typing a quote resulted in `this is text""^;` which is not correct, and
should be `this is text"^;`.

This PR changes logic for auto close:

1. We now prevent auto-closing in case of brackets where start == end
when they're typed immediately after a word character. i.e. For, ``` `,
", ' ```.
2. Other bracket pairs like `{}, (), etc` continue to auto-close
regardless of preceding character. So, `func^` to `func()^` will keep
working.
3. Auto-closing in other contexts like after spaces, punctuation, etc.
will still work.

Before:

![before](https://github.com/user-attachments/assets/6be02c95-4c71-488b-901d-b7b98c4170a4)

After:

![after](https://github.com/user-attachments/assets/680ece4d-20cb-428c-b430-846da3a2d643)

Release Notes:

- Fixed auto-paired quotes being inserted when typing a quote
immediately next to a word character.

Change summary

crates/editor/src/editor.rs       | 12 ++++++++++++
crates/editor/src/editor_tests.rs | 23 ++++++++++++++++++++---
2 files changed, 32 insertions(+), 3 deletions(-)

Detailed changes

crates/editor/src/editor.rs ๐Ÿ”—

@@ -2918,6 +2918,17 @@ impl Editor {
                                 .next()
                                 .map_or(true, |c| scope.should_autoclose_before(c));
 
+                            let preceding_text_allows_autoclose = selection.start.column == 0
+                                || snapshot.reversed_chars_at(selection.start).next().map_or(
+                                    true,
+                                    |c| {
+                                        bracket_pair.start != bracket_pair.end
+                                            || !snapshot
+                                                .char_classifier_at(selection.start)
+                                                .is_word(c)
+                                    },
+                                );
+
                             let is_closing_quote = if bracket_pair.end == bracket_pair.start
                                 && bracket_pair.start.len() == 1
                             {
@@ -2935,6 +2946,7 @@ impl Editor {
                             if autoclose
                                 && bracket_pair.close
                                 && following_text_allows_autoclose
+                                && preceding_text_allows_autoclose
                                 && !is_closing_quote
                             {
                                 let anchor = snapshot.anchor_before(selection.end);

crates/editor/src/editor_tests.rs ๐Ÿ”—

@@ -6357,12 +6357,29 @@ async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
     cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
     cx.assert_editor_state("{ยซaห‡ยป} b");
 
-    // Autclose pair where the start and end characters are the same
+    // Autoclose when not immediately after a word character
+    cx.set_state("a ห‡");
+    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
+    cx.assert_editor_state("a \"ห‡\"");
+
+    // Autoclose pair where the start and end characters are the same
+    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
+    cx.assert_editor_state("a \"\"ห‡");
+
+    // Don't autoclose when immediately after a word character
     cx.set_state("aห‡");
     cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
-    cx.assert_editor_state("a\"ห‡\"");
+    cx.assert_editor_state("a\"ห‡");
+
+    // Do autoclose when after a non-word character
+    cx.set_state("{ห‡");
     cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
-    cx.assert_editor_state("a\"\"ห‡");
+    cx.assert_editor_state("{\"ห‡\"");
+
+    // Non identical pairs autoclose regardless of preceding character
+    cx.set_state("aห‡");
+    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
+    cx.assert_editor_state("a{ห‡}");
 
     // Don't autoclose pair if autoclose is disabled
     cx.set_state("ห‡");