Add MultiBufferSnapshot::range_contains_excerpt_boundary

Max Brunsfeld and Nathan Sobo created

Use this method to disable move_line_down across excerpt boundaries.

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/editor/src/editor.rs       | 109 +-------------------------------
crates/editor/src/multi_buffer.rs |  23 ++++++
2 files changed, 27 insertions(+), 105 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -1783,104 +1783,6 @@ impl Editor {
     }
 
     pub fn move_line_down(&mut self, _: &MoveLineDown, cx: &mut ViewContext<Self>) {
-        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
-        let buffer = self.buffer.read(cx).snapshot(cx);
-
-        let mut deletes = Vec::new();
-        let mut inserts = Vec::new();
-        // let mut new_selection_ranges = Vec::new();
-        let mut unfold_ranges = Vec::new();
-        let mut refold_ranges = Vec::new();
-
-        let selections = self.local_selections::<Point>(cx);
-        let mut selections = selections.iter().peekable();
-        let mut contiguous_row_selections = Vec::new();
-        let mut new_selections = Vec::new();
-
-        while let Some(selection) = selections.next() {
-            // Find all the selections that span a contiguous row range
-            contiguous_row_selections.push(selection.clone());
-            let start_row = selection.start.row;
-            let mut end_row = if selection.end.column > 0 || selection.is_empty() {
-                display_map
-                    .next_row_boundary(selection.end.to_display_point(&display_map))
-                    .1
-                    .row
-                    + 1
-            } else {
-                selection.end.row
-            };
-
-            while let Some(next_selection) = selections.peek() {
-                if next_selection.start.row <= end_row {
-                    end_row = if next_selection.end.column > 0 || next_selection.is_empty() {
-                        display_map
-                            .next_row_boundary(next_selection.end.to_display_point(&display_map))
-                            .1
-                            .row
-                            + 1
-                    } else {
-                        next_selection.end.row
-                    };
-                    contiguous_row_selections.push(selections.next().unwrap().clone());
-                } else {
-                    break;
-                }
-            }
-
-            // Move the text spanned by the row range to be after the last line of the row range
-            if end_row <= buffer.max_point().row {
-                let range_to_move = Point::new(start_row, 0)..Point::new(end_row, 0);
-                let insertion_point = Point::new(end_row, buffer.line_len(end_row));
-                let insertion_row = insertion_point.row + 1;
-                let mut text = String::from("\n");
-                text.extend(buffer.text_for_range(range_to_move.clone()));
-                text.pop(); // Drop trailing newline
-                deletes.push(range_to_move.clone());
-                inserts.push((insertion_point, text));
-
-                // Make the selections relative to the insertion row
-                new_selections.extend(contiguous_row_selections.drain(..).map(|mut selection| {
-                    selection.start.row = insertion_row + selection.start.row - start_row;
-                    selection.end.row = insertion_row + selection.end.row - start_row;
-                    selection
-                }));
-
-                // Unfold all the folds spanned by these rows
-                unfold_ranges.push(range_to_move.clone());
-
-                // Refold ranges relative to the insertion row
-                for fold in display_map.folds_in_range(
-                    buffer.anchor_before(range_to_move.start)
-                        ..buffer.anchor_after(range_to_move.end),
-                ) {
-                    let mut start = fold.start.to_point(&buffer);
-                    let mut end = fold.end.to_point(&buffer);
-                    start.row = insertion_row + start.row - start_row;
-                    end.row = insertion_row + end.row - start_row;
-                    refold_ranges.push(start..end);
-                }
-            } else {
-                new_selections.extend(contiguous_row_selections.drain(..));
-            }
-        }
-
-        self.start_transaction(cx);
-        self.unfold_ranges(unfold_ranges, cx);
-        self.buffer.update(cx, |buffer, cx| {
-            for (point, text) in inserts.into_iter().rev() {
-                buffer.edit([point..point], text, cx);
-            }
-        });
-        self.fold_ranges(refold_ranges, cx);
-        self.update_selections(new_selections, Some(Autoscroll::Fit), cx);
-        self.buffer.update(cx, |buffer, cx| {
-            buffer.edit(deletes.into_iter().rev(), "", cx);
-        });
-        self.end_transaction(cx);
-    }
-
-    pub fn move_line_down2(&mut self, _: &MoveLineDown, cx: &mut ViewContext<Self>) {
         let selections = self.local_selections::<Point>(cx);
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = self.buffer.read(cx).snapshot(cx);
@@ -1916,9 +1818,6 @@ impl Editor {
                 }
             }
 
-            println!("spanned buffer rows {:?}", buffer_rows);
-            println!("spanned display rows {:?}", display_rows);
-
             // Cut the text from the selected rows and paste it at the end of the next line.
             if display_rows.end <= display_map.max_point().row() {
                 let start = Point::new(buffer_rows.start, 0).to_offset(&buffer);
@@ -1930,8 +1829,10 @@ impl Editor {
                 let next_row_buffer_end = display_map.next_row_boundary(next_row_display_end).1;
                 let next_row_buffer_end_offset = next_row_buffer_end.to_offset(&buffer);
 
-                dbg!(next_row_display_end);
-                dbg!(next_row_buffer_end);
+                if buffer.range_contains_excerpt_boundary(start..next_row_buffer_end_offset) {
+                    new_selection_ranges.extend(contiguous_selections.drain(..));
+                    continue;
+                }
 
                 let mut text = String::new();
                 text.push('\n');
@@ -1962,7 +1863,6 @@ impl Editor {
         }
 
         self.start_transaction(cx);
-
         self.unfold_ranges(old_folds, cx);
         self.buffer.update(cx, |buffer, cx| {
             for (range, text) in edits.into_iter().rev() {
@@ -1971,7 +1871,6 @@ impl Editor {
         });
         self.fold_ranges(new_folds, cx);
         self.select_ranges(new_selection_ranges, Some(Autoscroll::Fit), cx);
-
         self.end_transaction(cx);
     }
 

crates/editor/src/multi_buffer.rs 🔗

@@ -1395,6 +1395,23 @@ impl MultiBufferSnapshot {
         panic!("excerpt not found");
     }
 
+    pub fn range_contains_excerpt_boundary<T: ToOffset>(&self, range: Range<T>) -> bool {
+        let start = range.start.to_offset(self);
+        let end = range.end.to_offset(self);
+        let mut cursor = self.excerpts.cursor::<(usize, Option<&ExcerptId>)>();
+        cursor.seek(&start, Bias::Right, &());
+        let start_id = cursor
+            .item()
+            .or_else(|| cursor.prev_item())
+            .map(|excerpt| &excerpt.id);
+        cursor.seek_forward(&end, Bias::Right, &());
+        let end_id = cursor
+            .item()
+            .or_else(|| cursor.prev_item())
+            .map(|excerpt| &excerpt.id);
+        start_id != end_id
+    }
+
     pub fn parse_count(&self) -> usize {
         self.parse_count
     }
@@ -2158,6 +2175,12 @@ mod tests {
         );
         assert_eq!(snapshot.buffer_rows(4).collect::<Vec<_>>(), [Some(3)]);
         assert_eq!(snapshot.buffer_rows(5).collect::<Vec<_>>(), []);
+        assert!(!snapshot.range_contains_excerpt_boundary(Point::new(1, 0)..Point::new(1, 5)));
+        assert!(snapshot.range_contains_excerpt_boundary(Point::new(1, 0)..Point::new(2, 0)));
+        assert!(snapshot.range_contains_excerpt_boundary(Point::new(1, 0)..Point::new(4, 0)));
+        assert!(!snapshot.range_contains_excerpt_boundary(Point::new(2, 0)..Point::new(3, 0)));
+        assert!(!snapshot.range_contains_excerpt_boundary(Point::new(4, 0)..Point::new(4, 2)));
+        assert!(!snapshot.range_contains_excerpt_boundary(Point::new(4, 2)..Point::new(4, 2)));
 
         buffer_1.update(cx, |buffer, cx| {
             buffer.edit(