Fix autoclose error when cursor was at column 0

Max Brunsfeld created

Change summary

crates/editor/src/editor.rs | 259 ++++++++++++++------------------------
1 file changed, 99 insertions(+), 160 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -569,6 +569,7 @@ struct SelectNextState {
     done: bool,
 }
 
+#[derive(Debug)]
 struct AutocloseRegion {
     selection_id: usize,
     range: Range<Anchor>,
@@ -1883,19 +1884,20 @@ impl Editor {
                             // If the inserted text is a suffix of an opening bracket and the
                             // selection is preceded by the rest of the opening bracket, then
                             // insert the closing bracket.
-                            let should_autoclose = selection.start.column > (prefix_len as u32)
-                                && snapshot.contains_str_at(
-                                    Point::new(
-                                        selection.start.row,
-                                        selection.start.column - (prefix_len as u32),
-                                    ),
-                                    &bracket_pair.start[..prefix_len],
-                                )
-                                && snapshot
-                                    .chars_at(selection.start)
-                                    .next()
-                                    .map_or(true, |c| language.should_autoclose_before(c));
-                            if should_autoclose {
+                            let following_text_allows_autoclose = snapshot
+                                .chars_at(selection.start)
+                                .next()
+                                .map_or(true, |c| language.should_autoclose_before(c));
+                            let preceding_text_matches_prefix = prefix_len == 0
+                                || (selection.start.column >= (prefix_len as u32)
+                                    && snapshot.contains_str_at(
+                                        Point::new(
+                                            selection.start.row,
+                                            selection.start.column - (prefix_len as u32),
+                                        ),
+                                        &bracket_pair.start[..prefix_len],
+                                    ));
+                            if following_text_allows_autoclose && preceding_text_matches_prefix {
                                 let anchor = snapshot.anchor_before(selection.end);
                                 new_selections
                                     .push((selection.map(|_| anchor.clone()), text.len()));
@@ -2210,14 +2212,14 @@ impl Editor {
         buffer: &'a MultiBufferSnapshot,
     ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
         let mut i = 0;
-        let mut pair_states = self.autoclose_regions.as_slice();
+        let mut regions = self.autoclose_regions.as_slice();
         selections.into_iter().map(move |selection| {
             let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
 
             let mut enclosing = None;
-            while let Some(pair_state) = pair_states.get(i) {
+            while let Some(pair_state) = regions.get(i) {
                 if pair_state.range.end.to_offset(buffer) < range.start {
-                    pair_states = &pair_states[i + 1..];
+                    regions = &regions[i + 1..];
                     i = 0;
                 } else if pair_state.range.start.to_offset(buffer) > range.end {
                     break;
@@ -9594,7 +9596,8 @@ mod tests {
 
     #[gpui::test]
     async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
+        let mut cx = EditorTestContext::new(cx);
+
         let language = Arc::new(Language::new(
             LanguageConfig {
                 brackets: vec![
@@ -9623,165 +9626,101 @@ mod tests {
             Some(tree_sitter_rust::language()),
         ));
 
-        let text = r#"
-            a
-
-            /
-
-        "#
-        .unindent();
-
-        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
-        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-        let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
-        view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
-            .await;
+        let registry = Arc::new(LanguageRegistry::test());
+        registry.add(language.clone());
+        cx.update_buffer(|buffer, cx| {
+            buffer.set_language_registry(registry);
+            buffer.set_language(Some(language), cx);
+        });
 
-        view.update(cx, |view, cx| {
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
-                    DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
-                ])
-            });
+        cx.set_state(
+            &r#"
+                🏀ˇ
+                εˇ
+                ❤️ˇ
+            "#
+            .unindent(),
+        );
 
+        // autoclose multiple nested brackets at multiple cursors
+        cx.update_editor(|view, cx| {
             view.handle_input("{", cx);
             view.handle_input("{", cx);
             view.handle_input("{", cx);
-            assert_eq!(
-                view.text(cx),
-                "
-                {{{}}}
-                {{{}}}
-                /
-
-                "
-                .unindent()
-            );
+        });
+        cx.assert_editor_state(
+            &"
+                🏀{{{ˇ}}}
+                ε{{{ˇ}}}
+                ❤️{{{ˇ}}}
+            "
+            .unindent(),
+        );
 
+        // skip over the auto-closed brackets when typing a closing bracket
+        cx.update_editor(|view, cx| {
             view.move_right(&MoveRight, cx);
             view.handle_input("}", cx);
             view.handle_input("}", cx);
             view.handle_input("}", cx);
-            assert_eq!(
-                view.text(cx),
-                "
-                {{{}}}}
-                {{{}}}}
-                /
-
-                "
-                .unindent()
-            );
+        });
+        cx.assert_editor_state(
+            &"
+                🏀{{{}}}}ˇ
+                ε{{{}}}}ˇ
+                ❤️{{{}}}}ˇ
+            "
+            .unindent(),
+        );
 
-            view.undo(&Undo, cx);
+        // autoclose multi-character pairs
+        cx.set_state(
+            &"
+                ˇ
+                ˇ
+            "
+            .unindent(),
+        );
+        cx.update_editor(|view, cx| {
             view.handle_input("/", cx);
             view.handle_input("*", cx);
-            assert_eq!(
-                view.text(cx),
-                "
-                /* */
-                /* */
-                /
-
-                "
-                .unindent()
-            );
-
-            view.undo(&Undo, cx);
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([
-                    DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
-                    DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
-                ])
-            });
-            view.handle_input("*", cx);
-            assert_eq!(
-                view.text(cx),
-                "
-                a
-
-                /*
-                *
-                "
-                .unindent()
-            );
-
-            // Don't autoclose if the next character isn't whitespace and isn't
-            // listed in the language's "autoclose_before" section.
-            view.finalize_last_transaction(cx);
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)])
-            });
-            view.handle_input("{", cx);
-            assert_eq!(
-                view.text(cx),
-                "
-                {a
-
-                /*
-                *
-                "
-                .unindent()
-            );
-
-            view.undo(&Undo, cx);
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1)])
-            });
-            view.handle_input("{", cx);
-            assert_eq!(
-                view.text(cx),
-                "
-                {a}
+        });
+        cx.assert_editor_state(
+            &"
+                /*ˇ */
+                /*ˇ */
+            "
+            .unindent(),
+        );
 
-                /*
-                *
-                "
-                .unindent()
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]
-            );
+        // one cursor autocloses a multi-character pair, one cursor
+        // does not autoclose.
+        cx.set_state(
+            &"
+                /ˇ
+                ˇ
+            "
+            .unindent(),
+        );
+        cx.update_editor(|view, cx| view.handle_input("*", cx));
+        cx.assert_editor_state(
+            &"
+                /*ˇ */
+                *ˇ
+            "
+            .unindent(),
+        );
 
-            view.undo(&Undo, cx);
-            view.handle_input("[", cx);
-            assert_eq!(
-                view.text(cx),
-                "
-                [a]
-                
-                /*
-                *
-                "
-                .unindent()
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]
-            );
+        // Don't autoclose if the next character isn't whitespace and isn't
+        // listed in the language's "autoclose_before" section.
+        cx.set_state("ˇa b");
+        cx.update_editor(|view, cx| view.handle_input("{", cx));
+        cx.assert_editor_state("{ˇa b");
 
-            view.undo(&Undo, cx);
-            view.change_selections(None, cx, |s| {
-                s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)])
-            });
-            view.handle_input("[", cx);
-            assert_eq!(
-                view.text(cx),
-                "
-                a[
-                
-                /*
-                *
-                "
-                .unindent()
-            );
-            assert_eq!(
-                view.selections.display_ranges(cx),
-                [DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2)]
-            );
-        });
+        // Surround with brackets if text is selected
+        cx.set_state("«aˇ» b");
+        cx.update_editor(|view, cx| view.handle_input("{", cx));
+        cx.assert_editor_state("{«aˇ»} b");
     }
 
     #[gpui::test]