Merge pull request #980 from zed-industries/coerce-multibuffer-changes

Keith Simmons created

Filter overlapping multibuffer edits

Change summary

crates/editor/src/editor.rs         | 70 ++++++++++++++----------------
crates/editor/src/multi_buffer.rs   | 10 +--
crates/util/src/test/marked_text.rs | 16 +++++-
3 files changed, 48 insertions(+), 48 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -9801,55 +9801,49 @@ mod tests {
     #[gpui::test]
     fn test_editing_overlapping_excerpts(cx: &mut gpui::MutableAppContext) {
         cx.set_global(Settings::test(cx));
-        let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
+        let (initial_text, excerpt_ranges) = marked_text_ranges(indoc! {"
+                [aaaa
+                (bbbb]
+                cccc)"});
+        let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
         let multibuffer = cx.add_model(|cx| {
             let mut multibuffer = MultiBuffer::new(0);
-            multibuffer.push_excerpts(
-                buffer,
-                [
-                    Point::new(0, 0)..Point::new(1, 4),
-                    Point::new(1, 0)..Point::new(2, 4),
-                ],
-                cx,
-            );
+            multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
             multibuffer
         });
 
-        assert_eq!(
-            multibuffer.read(cx).read(cx).text(),
-            "aaaa\nbbbb\nbbbb\ncccc"
-        );
-
         let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
         view.update(cx, |view, cx| {
-            view.select_ranges(
-                [
-                    Point::new(1, 1)..Point::new(1, 1),
-                    Point::new(2, 3)..Point::new(2, 3),
-                ],
-                None,
-                cx,
-            );
+            let (expected_text, selection_ranges) = marked_text_ranges(indoc! {"
+                aaaa
+                b|bbb
+                b|bb|b
+                cccc"});
+            assert_eq!(view.text(cx), expected_text);
+            view.select_ranges(selection_ranges, None, cx);
 
             view.handle_input(&Input("X".to_string()), cx);
-            assert_eq!(view.text(cx), "aaaa\nbXbbXb\nbXbbXb\ncccc");
-            assert_eq!(
-                view.selected_ranges(cx),
-                [
-                    Point::new(1, 2)..Point::new(1, 2),
-                    Point::new(2, 5)..Point::new(2, 5),
-                ]
-            );
+
+            let (expected_text, expected_selections) = marked_text_ranges(indoc! {"
+                aaaa
+                bX|bbXb
+                bX|bbX|b
+                cccc"});
+            assert_eq!(view.text(cx), expected_text);
+            assert_eq!(view.selected_ranges(cx), expected_selections);
 
             view.newline(&Newline, cx);
-            assert_eq!(view.text(cx), "aaaa\nbX\nbbX\nb\nbX\nbbX\nb\ncccc");
-            assert_eq!(
-                view.selected_ranges(cx),
-                [
-                    Point::new(2, 0)..Point::new(2, 0),
-                    Point::new(6, 0)..Point::new(6, 0),
-                ]
-            );
+            let (expected_text, expected_selections) = marked_text_ranges(indoc! {"
+                aaaa
+                bX
+                |bbX
+                b
+                bX
+                |bbX
+                |b
+                cccc"});
+            assert_eq!(view.text(cx), expected_text);
+            assert_eq!(view.selected_ranges(cx), expected_selections);
         });
     }
 

crates/editor/src/multi_buffer.rs 🔗

@@ -378,13 +378,11 @@ impl MultiBuffer {
                     let mut insertions = Vec::new();
                     let mut deletions = Vec::new();
                     let empty_str: Arc<str> = "".into();
-                    while let Some((mut range, mut new_text, mut is_insertion)) = edits.next() {
-                        while let Some((next_range, next_new_text, next_is_insertion)) =
-                            edits.peek()
-                        {
+                    while let Some((mut range, new_text, mut is_insertion)) = edits.next() {
+                        while let Some((next_range, _, next_is_insertion)) = edits.peek() {
                             if range.end >= next_range.start {
                                 range.end = cmp::max(next_range.end, range.end);
-                                new_text = format!("{new_text}{next_new_text}").into();
+
                                 is_insertion |= *next_is_insertion;
                                 edits.next();
                             } else {
@@ -395,7 +393,7 @@ impl MultiBuffer {
                         if is_insertion {
                             insertions.push((
                                 buffer.anchor_before(range.start)..buffer.anchor_before(range.end),
-                                new_text,
+                                new_text.clone(),
                             ));
                         } else if !range.is_empty() {
                             deletions.push((

crates/util/src/test/marked_text.rs 🔗

@@ -55,10 +55,18 @@ pub fn marked_text_ranges_by(
     (unmarked_text, range_lookup)
 }
 
-pub fn marked_text_ranges(marked_text: &str) -> (String, Vec<Range<usize>>) {
-    let (unmarked_text, mut ranges) = marked_text_ranges_by(marked_text, vec![('[', ']')]);
+// Returns ranges delimited by (), [], and <> ranges. Ranges using the same markers
+// must not be overlapping. May also include | for empty ranges
+pub fn marked_text_ranges(full_marked_text: &str) -> (String, Vec<Range<usize>>) {
+    let (range_marked_text, empty_offsets) = marked_text(full_marked_text);
+    let (unmarked, range_lookup) =
+        marked_text_ranges_by(&range_marked_text, vec![('[', ']'), ('(', ')'), ('<', '>')]);
     (
-        unmarked_text,
-        ranges.remove(&('[', ']')).unwrap_or_else(Vec::new),
+        unmarked,
+        range_lookup
+            .into_values()
+            .flatten()
+            .chain(empty_offsets.into_iter().map(|offset| offset..offset))
+            .collect(),
     )
 }