WIP: Allow lines to be moved down across excerpts

Nathan Sobo created

This is still a bit weird because we can't remove the last line of an excerpt but we still move it into another buffer. There also seem to be issues with undo.

Change summary

crates/editor/src/editor.rs | 118 +++++++++++++++++++++++++++++++++++++-
1 file changed, 112 insertions(+), 6 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -1783,8 +1783,104 @@ 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);
@@ -1799,10 +1895,12 @@ impl Editor {
         while let Some(selection) = selections.next() {
             // Accumulate contiguous regions of rows that we want to move.
             contiguous_selections.push(selection.point_range(&buffer));
+
             let SpannedRows {
                 mut buffer_rows,
                 mut display_rows,
             } = selection.spanned_rows(false, &display_map);
+
             while let Some(next_selection) = selections.peek() {
                 let SpannedRows {
                     buffer_rows: next_buffer_rows,
@@ -1818,6 +1916,9 @@ 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);
@@ -1829,27 +1930,30 @@ 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);
+
                 let mut text = String::new();
                 text.push('\n');
                 text.extend(buffer.text_for_range(start..end));
                 edits.push((start..end + 1, String::new()));
                 edits.push((next_row_buffer_end_offset..next_row_buffer_end_offset, text));
 
-                let row_delta = next_row_buffer_end.row - buffer_rows.end + 1;
-
                 // Move selections down.
+                let display_row_delta = next_row_display_end.row() - display_rows.end + 1;
                 for range in &mut contiguous_selections {
-                    range.start.row += row_delta;
-                    range.end.row += row_delta;
+                    range.start.row += display_row_delta;
+                    range.end.row += display_row_delta;
                 }
 
                 // Move folds down.
                 old_folds.push(start..end);
+                let buffer_row_delta = next_row_buffer_end.row - buffer_rows.end + 1;
                 for fold in display_map.folds_in_range(start..end) {
                     let mut start = fold.start.to_point(&buffer);
                     let mut end = fold.end.to_point(&buffer);
-                    start.row += row_delta;
-                    end.row += row_delta;
+                    start.row += buffer_row_delta;
+                    end.row += buffer_row_delta;
                     new_folds.push(start..end);
                 }
             }
@@ -1857,6 +1961,8 @@ impl Editor {
             new_selection_ranges.extend(contiguous_selections.drain(..));
         }
 
+        self.start_transaction(cx);
+
         self.unfold_ranges(old_folds, cx);
         self.buffer.update(cx, |buffer, cx| {
             for (range, text) in edits.into_iter().rev() {