Merge pull request #37 from zed-industries/move-line-up-down

Nathan Sobo created

Move line up and down

Change summary

zed/src/editor/buffer/selection.rs     |  13 
zed/src/editor/buffer_view.rs          | 297 ++++++++++++++++++++++++---
zed/src/editor/display_map/fold_map.rs | 193 +++++++++++++----
zed/src/editor/display_map/mod.rs      |  12 +
4 files changed, 426 insertions(+), 89 deletions(-)

Detailed changes

zed/src/editor/buffer/selection.rs 🔗

@@ -73,14 +73,18 @@ impl Selection {
         }
     }
 
-    pub fn buffer_rows_for_display_rows(&self, map: &DisplayMap, ctx: &AppContext) -> Range<u32> {
+    pub fn buffer_rows_for_display_rows(
+        &self,
+        map: &DisplayMap,
+        ctx: &AppContext,
+    ) -> (Range<u32>, Range<u32>) {
         let display_start = self.start.to_display_point(map, ctx).unwrap();
         let buffer_start = DisplayPoint::new(display_start.row(), 0)
             .to_buffer_point(map, Bias::Left, ctx)
             .unwrap();
 
         let mut display_end = self.end.to_display_point(map, ctx).unwrap();
-        if display_end != map.max_point(ctx)
+        if display_end.row() != map.max_point(ctx).row()
             && display_start.row() != display_end.row()
             && display_end.column() == 0
         {
@@ -93,6 +97,9 @@ impl Selection {
         .to_buffer_point(map, Bias::Left, ctx)
         .unwrap();
 
-        buffer_start.row..buffer_end.row + 1
+        (
+            buffer_start.row..buffer_end.row + 1,
+            display_start.row()..display_end.row() + 1,
+        )
     }
 }

zed/src/editor/buffer_view.rs 🔗

@@ -19,6 +19,7 @@ use std::{
     cmp::{self, Ordering},
     fmt::Write,
     iter::FromIterator,
+    mem,
     ops::Range,
     path::Path,
     sync::Arc,
@@ -56,6 +57,8 @@ pub fn init(app: &mut MutableAppContext) {
             Some("BufferView"),
         ),
         Binding::new("cmd-shift-D", "buffer:duplicate_line", Some("BufferView")),
+        Binding::new("ctrl-cmd-up", "buffer:move_line_up", Some("BufferView")),
+        Binding::new("ctrl-cmd-down", "buffer:move_line_down", Some("BufferView")),
         Binding::new("cmd-x", "buffer:cut", Some("BufferView")),
         Binding::new("cmd-c", "buffer:copy", Some("BufferView")),
         Binding::new("cmd-v", "buffer:paste", Some("BufferView")),
@@ -171,6 +174,8 @@ pub fn init(app: &mut MutableAppContext) {
         BufferView::delete_to_end_of_line,
     );
     app.add_action("buffer:duplicate_line", BufferView::duplicate_line);
+    app.add_action("buffer:move_line_up", BufferView::move_line_up);
+    app.add_action("buffer:move_line_down", BufferView::move_line_down);
     app.add_action("buffer:cut", BufferView::cut);
     app.add_action("buffer:copy", BufferView::copy);
     app.add_action("buffer:paste", BufferView::paste);
@@ -518,23 +523,30 @@ impl BufferView {
         self.pending_selection.is_some()
     }
 
-    #[cfg(test)]
-    fn select_ranges<'a, T>(&mut self, ranges: T, ctx: &mut ViewContext<Self>) -> Result<()>
+    fn select_ranges<I, T>(&mut self, ranges: I, autoscroll: bool, ctx: &mut ViewContext<Self>)
     where
-        T: IntoIterator<Item = &'a Range<usize>>,
+        I: IntoIterator<Item = Range<T>>,
+        T: ToOffset,
     {
         let buffer = self.buffer.read(ctx);
         let mut selections = Vec::new();
         for range in ranges {
+            let mut start = range.start.to_offset(buffer).unwrap();
+            let mut end = range.end.to_offset(buffer).unwrap();
+            let reversed = if start > end {
+                mem::swap(&mut start, &mut end);
+                true
+            } else {
+                false
+            };
             selections.push(Selection {
-                start: buffer.anchor_before(range.start)?,
-                end: buffer.anchor_before(range.end)?,
-                reversed: false,
+                start: buffer.anchor_before(start).unwrap(),
+                end: buffer.anchor_before(end).unwrap(),
+                reversed,
                 goal_column: None,
             });
         }
-        self.update_selections(selections, false, ctx);
-        Ok(())
+        self.update_selections(selections, autoscroll, ctx);
     }
 
     #[cfg(test)]
@@ -542,8 +554,6 @@ impl BufferView {
     where
         T: IntoIterator<Item = &'a Range<DisplayPoint>>,
     {
-        use std::mem;
-
         let map = self.display_map.read(ctx);
         let mut selections = Vec::new();
         for range in ranges {
@@ -693,7 +703,7 @@ impl BufferView {
 
         let mut selections = self.selections(app).iter().peekable();
         while let Some(selection) = selections.next() {
-            let mut rows = selection.buffer_rows_for_display_rows(map, app);
+            let (mut rows, _) = selection.buffer_rows_for_display_rows(map, app);
             let goal_display_column = selection
                 .head()
                 .to_display_point(map, app)
@@ -702,7 +712,7 @@ impl BufferView {
 
             // Accumulate contiguous regions of rows that we want to delete.
             while let Some(next_selection) = selections.peek() {
-                let next_rows = next_selection.buffer_rows_for_display_rows(map, app);
+                let (next_rows, _) = next_selection.buffer_rows_for_display_rows(map, app);
                 if next_rows.start <= rows.end {
                     rows.end = next_rows.end;
                     selections.next().unwrap();
@@ -750,10 +760,10 @@ impl BufferView {
                 goal_column: None,
             })
             .collect();
-        self.update_selections(new_selections, true, ctx);
         self.buffer
             .update(ctx, |buffer, ctx| buffer.edit(edit_ranges, "", Some(ctx)))
             .unwrap();
+        self.update_selections(new_selections, true, ctx);
         self.end_transaction(ctx);
     }
 
@@ -780,9 +790,9 @@ impl BufferView {
         let mut selections_iter = selections.iter_mut().peekable();
         while let Some(selection) = selections_iter.next() {
             // Avoid duplicating the same lines twice.
-            let mut rows = selection.buffer_rows_for_display_rows(map, app);
+            let (mut rows, _) = selection.buffer_rows_for_display_rows(map, app);
             while let Some(next_selection) = selections_iter.peek() {
-                let next_rows = next_selection.buffer_rows_for_display_rows(map, app);
+                let (next_rows, _) = next_selection.buffer_rows_for_display_rows(map, app);
                 if next_rows.start <= rows.end - 1 {
                     rows.end = next_rows.end;
                     selections_iter.next().unwrap();
@@ -811,14 +821,216 @@ impl BufferView {
         // Restore bias on selections.
         let buffer = self.buffer.read(ctx);
         for selection in &mut selections {
-            selection.start = selection.start.bias_right(buffer).unwrap();
-            selection.end = selection.end.bias_right(buffer).unwrap();
+            selection.start = selection.start.bias_left(buffer).unwrap();
+            selection.end = selection.end.bias_left(buffer).unwrap();
         }
         self.update_selections(selections, true, ctx);
 
         self.end_transaction(ctx);
     }
 
+    pub fn move_line_up(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+        self.start_transaction(ctx);
+
+        let app = ctx.as_ref();
+        let buffer = self.buffer.read(ctx);
+        let map = self.display_map.read(ctx);
+
+        let mut edits = Vec::new();
+        let mut new_selection_ranges = Vec::new();
+        let mut old_folds = Vec::new();
+        let mut new_folds = Vec::new();
+
+        let mut selections = self.selections(app).iter().peekable();
+        let mut contiguous_selections = Vec::new();
+        while let Some(selection) = selections.next() {
+            // Accumulate contiguous regions of rows that we want to move.
+            contiguous_selections.push(selection.range(buffer));
+            let (mut buffer_rows, mut display_rows) =
+                selection.buffer_rows_for_display_rows(map, app);
+            while let Some(next_selection) = selections.peek() {
+                let (next_buffer_rows, next_display_rows) =
+                    next_selection.buffer_rows_for_display_rows(map, app);
+                if next_buffer_rows.start <= buffer_rows.end {
+                    buffer_rows.end = next_buffer_rows.end;
+                    display_rows.end = next_display_rows.end;
+                    contiguous_selections.push(next_selection.range(buffer));
+                    selections.next().unwrap();
+                } else {
+                    break;
+                }
+            }
+
+            // Cut the text from the selected rows and paste it at the start of the previous line.
+            if display_rows.start != 0 {
+                let selection_row_start =
+                    Point::new(buffer_rows.start, 0).to_offset(buffer).unwrap();
+                let selection_row_end = Point::new(
+                    buffer_rows.end - 1,
+                    buffer.line_len(buffer_rows.end - 1).unwrap(),
+                )
+                .to_offset(buffer)
+                .unwrap();
+
+                let prev_row_display_start = DisplayPoint::new(display_rows.start - 1, 0);
+                let prev_row_start = prev_row_display_start
+                    .to_buffer_offset(map, Bias::Left, app)
+                    .unwrap();
+
+                let mut text = String::new();
+                text.extend(
+                    buffer
+                        .text_for_range(selection_row_start..selection_row_end)
+                        .unwrap(),
+                );
+                text.push('\n');
+                edits.push((prev_row_start..prev_row_start, text));
+                edits.push((selection_row_start - 1..selection_row_end, String::new()));
+
+                let row_delta = buffer_rows.start
+                    - prev_row_display_start
+                        .to_buffer_point(map, Bias::Left, app)
+                        .unwrap()
+                        .row;
+
+                // Move selections up.
+                for range in &mut contiguous_selections {
+                    range.start.row -= row_delta;
+                    range.end.row -= row_delta;
+                }
+
+                // Move folds up.
+                old_folds.push(selection_row_start..selection_row_end);
+                for fold in map
+                    .folds_in_range(selection_row_start..selection_row_end, app)
+                    .unwrap()
+                {
+                    let mut start = fold.start.to_point(buffer).unwrap();
+                    let mut end = fold.end.to_point(buffer).unwrap();
+                    start.row -= row_delta;
+                    end.row -= row_delta;
+                    new_folds.push(start..end);
+                }
+            }
+
+            new_selection_ranges.extend(contiguous_selections.drain(..));
+        }
+
+        self.unfold_ranges(old_folds, ctx);
+        self.buffer.update(ctx, |buffer, ctx| {
+            for (range, text) in edits.into_iter().rev() {
+                buffer.edit(Some(range), text, Some(ctx)).unwrap();
+            }
+        });
+        self.fold_ranges(new_folds, ctx);
+        self.select_ranges(new_selection_ranges, true, ctx);
+
+        self.end_transaction(ctx);
+    }
+
+    pub fn move_line_down(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+        self.start_transaction(ctx);
+
+        let app = ctx.as_ref();
+        let buffer = self.buffer.read(ctx);
+        let map = self.display_map.read(ctx);
+
+        let mut edits = Vec::new();
+        let mut new_selection_ranges = Vec::new();
+        let mut old_folds = Vec::new();
+        let mut new_folds = Vec::new();
+
+        let mut selections = self.selections(app).iter().peekable();
+        let mut contiguous_selections = Vec::new();
+        while let Some(selection) = selections.next() {
+            // Accumulate contiguous regions of rows that we want to move.
+            contiguous_selections.push(selection.range(buffer));
+            let (mut buffer_rows, mut display_rows) =
+                selection.buffer_rows_for_display_rows(map, app);
+            while let Some(next_selection) = selections.peek() {
+                let (next_buffer_rows, next_display_rows) =
+                    next_selection.buffer_rows_for_display_rows(map, app);
+                if next_buffer_rows.start <= buffer_rows.end {
+                    buffer_rows.end = next_buffer_rows.end;
+                    display_rows.end = next_display_rows.end;
+                    contiguous_selections.push(next_selection.range(buffer));
+                    selections.next().unwrap();
+                } else {
+                    break;
+                }
+            }
+
+            // Cut the text from the selected rows and paste it at the end of the next line.
+            if display_rows.end <= map.max_point(app).row() {
+                let selection_row_start =
+                    Point::new(buffer_rows.start, 0).to_offset(buffer).unwrap();
+                let selection_row_end = Point::new(
+                    buffer_rows.end - 1,
+                    buffer.line_len(buffer_rows.end - 1).unwrap(),
+                )
+                .to_offset(buffer)
+                .unwrap();
+
+                let next_row_display_end = DisplayPoint::new(
+                    display_rows.end,
+                    map.line_len(display_rows.end, app).unwrap(),
+                );
+                let next_row_end = next_row_display_end
+                    .to_buffer_offset(map, Bias::Left, app)
+                    .unwrap();
+
+                let mut text = String::new();
+                text.push('\n');
+                text.extend(
+                    buffer
+                        .text_for_range(selection_row_start..selection_row_end)
+                        .unwrap(),
+                );
+                edits.push((selection_row_start..selection_row_end + 1, String::new()));
+                edits.push((next_row_end..next_row_end, text));
+
+                let row_delta = next_row_display_end
+                    .to_buffer_point(map, Bias::Right, app)
+                    .unwrap()
+                    .row
+                    - buffer_rows.end
+                    + 1;
+
+                // Move selections down.
+                for range in &mut contiguous_selections {
+                    range.start.row += row_delta;
+                    range.end.row += row_delta;
+                }
+
+                // Move folds down.
+                old_folds.push(selection_row_start..selection_row_end);
+                for fold in map
+                    .folds_in_range(selection_row_start..selection_row_end, app)
+                    .unwrap()
+                {
+                    let mut start = fold.start.to_point(buffer).unwrap();
+                    let mut end = fold.end.to_point(buffer).unwrap();
+                    start.row += row_delta;
+                    end.row += row_delta;
+                    new_folds.push(start..end);
+                }
+            }
+
+            new_selection_ranges.extend(contiguous_selections.drain(..));
+        }
+
+        self.unfold_ranges(old_folds, ctx);
+        self.buffer.update(ctx, |buffer, ctx| {
+            for (range, text) in edits.into_iter().rev() {
+                buffer.edit(Some(range), text, Some(ctx)).unwrap();
+            }
+        });
+        self.fold_ranges(new_folds, ctx);
+        self.select_ranges(new_selection_ranges, true, ctx);
+
+        self.end_transaction(ctx);
+    }
+
     pub fn cut(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
         self.start_transaction(ctx);
         let mut text = String::new();
@@ -1313,9 +1525,11 @@ impl BufferView {
     }
 
     pub fn move_to_beginning(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+        let buffer = self.buffer.read(ctx);
+        let cursor = buffer.anchor_before(Point::new(0, 0)).unwrap();
         let selection = Selection {
-            start: Anchor::Start,
-            end: Anchor::Start,
+            start: cursor.clone(),
+            end: cursor,
             reversed: false,
             goal_column: None,
         };
@@ -1329,9 +1543,11 @@ impl BufferView {
     }
 
     pub fn move_to_end(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+        let buffer = self.buffer.read(ctx);
+        let cursor = buffer.anchor_before(buffer.max_point()).unwrap();
         let selection = Selection {
-            start: Anchor::End,
-            end: Anchor::End,
+            start: cursor.clone(),
+            end: cursor,
             reversed: false,
             goal_column: None,
         };
@@ -1495,12 +1711,7 @@ impl BufferView {
             }
         }
 
-        if !fold_ranges.is_empty() {
-            self.display_map.update(ctx, |map, ctx| {
-                map.fold(fold_ranges, ctx).unwrap();
-            });
-            *self.autoscroll_requested.lock() = true;
-        }
+        self.fold_ranges(fold_ranges, ctx);
     }
 
     pub fn unfold(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
@@ -1521,11 +1732,7 @@ impl BufferView {
                 start..end
             })
             .collect::<Vec<_>>();
-
-        self.display_map.update(ctx, |map, ctx| {
-            map.unfold(ranges, ctx).unwrap();
-        });
-        *self.autoscroll_requested.lock() = true;
+        self.unfold_ranges(ranges, ctx);
     }
 
     fn is_line_foldable(&self, display_row: u32, app: &AppContext) -> bool {
@@ -1583,6 +1790,24 @@ impl BufferView {
         });
     }
 
+    fn fold_ranges<T: ToOffset>(&mut self, ranges: Vec<Range<T>>, ctx: &mut ViewContext<Self>) {
+        if !ranges.is_empty() {
+            self.display_map.update(ctx, |map, ctx| {
+                map.fold(ranges, ctx).unwrap();
+            });
+            *self.autoscroll_requested.lock() = true;
+        }
+    }
+
+    fn unfold_ranges<T: ToOffset>(&mut self, ranges: Vec<Range<T>>, ctx: &mut ViewContext<Self>) {
+        if !ranges.is_empty() {
+            self.display_map.update(ctx, |map, ctx| {
+                map.unfold(ranges, ctx).unwrap();
+            });
+            *self.autoscroll_requested.lock() = true;
+        }
+    }
+
     pub fn line(&self, display_row: u32, app: &AppContext) -> Result<String> {
         self.display_map.read(app).line(display_row, app)
     }
@@ -2732,14 +2957,14 @@ mod tests {
 
             // Cut with three selections. Clipboard text is divided into three slices.
             view.update(app, |view, ctx| {
-                view.select_ranges(&[0..4, 8..14, 19..24], ctx).unwrap();
+                view.select_ranges(vec![0..4, 8..14, 19..24], false, ctx);
                 view.cut(&(), ctx);
             });
             assert_eq!(view.read(app).text(app.as_ref()), "two four six ");
 
             // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
             view.update(app, |view, ctx| {
-                view.select_ranges(&[4..4, 9..9, 13..13], ctx).unwrap();
+                view.select_ranges(vec![4..4, 9..9, 13..13], false, ctx);
                 view.paste(&(), ctx);
             });
             assert_eq!(
@@ -2759,7 +2984,7 @@ mod tests {
             // match the number of slices in the clipboard, the entire clipboard text
             // is pasted at each cursor.
             view.update(app, |view, ctx| {
-                view.select_ranges(&[0..0, 28..28], ctx).unwrap();
+                view.select_ranges(vec![0..0, 28..28], false, ctx);
                 view.insert(&"( ".to_string(), ctx);
                 view.paste(&(), ctx);
                 view.insert(&") ".to_string(), ctx);
@@ -2770,7 +2995,7 @@ mod tests {
             );
 
             view.update(app, |view, ctx| {
-                view.select_ranges(&[0..0], ctx).unwrap();
+                view.select_ranges(vec![0..0], false, ctx);
                 view.insert(&"123\n4567\n89\n".to_string(), ctx);
             });
             assert_eq!(

zed/src/editor/display_map/fold_map.rs 🔗

@@ -89,6 +89,33 @@ impl FoldMap {
         DisplayPoint(self.transforms.summary().display.rightmost_point)
     }
 
+    pub fn folds_in_range<T>(&self, range: Range<T>, app: &AppContext) -> Result<&[Range<Anchor>]>
+    where
+        T: ToOffset,
+    {
+        let buffer = self.buffer.read(app);
+        let range = buffer.anchor_before(range.start)?..buffer.anchor_before(range.end)?;
+        let mut start_ix = find_insertion_index(&self.folds, |probe| probe.cmp(&range, buffer))?;
+        let mut end_ix = start_ix;
+
+        for fold in self.folds[..start_ix].iter().rev() {
+            if fold.end.cmp(&range.start, buffer)? == Ordering::Greater {
+                start_ix -= 1;
+            } else {
+                break;
+            }
+        }
+        for fold in &self.folds[end_ix..] {
+            if range.end.cmp(&fold.start, buffer)? == Ordering::Greater {
+                end_ix += 1;
+            } else {
+                break;
+            }
+        }
+
+        Ok(&self.folds[start_ix..end_ix])
+    }
+
     pub fn fold<T: ToOffset>(
         &mut self,
         ranges: impl IntoIterator<Item = Range<T>>,
@@ -169,6 +196,13 @@ impl FoldMap {
         false
     }
 
+    pub fn to_buffer_offset(&self, point: DisplayPoint, app: &AppContext) -> Result<usize> {
+        let mut cursor = self.transforms.cursor::<DisplayPoint, TransformSummary>();
+        cursor.seek(&point, SeekBias::Right);
+        let overshoot = point.0 - cursor.start().display.lines;
+        (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(app))
+    }
+
     pub fn to_display_offset(
         &self,
         point: DisplayPoint,
@@ -235,7 +269,7 @@ impl FoldMap {
                     let next_edit = edits.next().unwrap();
                     delta += next_edit.delta();
 
-                    if next_edit.old_range.end > edit.old_range.end {
+                    if next_edit.old_range.end >= edit.old_range.end {
                         edit.old_range.end = next_edit.old_range.end;
                         cursor.seek(&edit.old_range.end, SeekBias::Right);
                         cursor.next();
@@ -415,7 +449,7 @@ impl<'a> Iterator for Chars<'a> {
             return Some(c);
         }
 
-        if self.offset == self.cursor.end().display.chars {
+        while self.offset == self.cursor.end().display.chars && self.cursor.item().is_some() {
             self.cursor.next();
         }
 
@@ -519,6 +553,50 @@ mod tests {
         });
     }
 
+    #[test]
+    fn test_adjacent_folds() {
+        App::test((), |app| {
+            let buffer = app.add_model(|ctx| Buffer::new(0, "abcdefghijkl", ctx));
+
+            {
+                let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+
+                map.fold(vec![5..8], app.as_ref()).unwrap();
+                map.check_invariants(app.as_ref());
+                assert_eq!(map.text(app.as_ref()), "abcde…ijkl");
+
+                // Create an fold adjacent to the start of the first fold.
+                map.fold(vec![0..1, 2..5], app.as_ref()).unwrap();
+                map.check_invariants(app.as_ref());
+                assert_eq!(map.text(app.as_ref()), "…b…ijkl");
+
+                // Create an fold adjacent to the end of the first fold.
+                map.fold(vec![11..11, 8..10], app.as_ref()).unwrap();
+                map.check_invariants(app.as_ref());
+                assert_eq!(map.text(app.as_ref()), "…b…kl");
+            }
+
+            {
+                let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+
+                // Create two adjacent folds.
+                map.fold(vec![0..2, 2..5], app.as_ref()).unwrap();
+                map.check_invariants(app.as_ref());
+                assert_eq!(map.text(app.as_ref()), "…fghijkl");
+
+                // Edit within one of the folds.
+                let edits = buffer.update(app, |buffer, ctx| {
+                    let version = buffer.version();
+                    buffer.edit(vec![0..1], "12345", Some(ctx)).unwrap();
+                    buffer.edits_since(version).collect::<Vec<_>>()
+                });
+                map.apply_edits(edits.as_slice(), app.as_ref()).unwrap();
+                map.check_invariants(app.as_ref());
+                assert_eq!(map.text(app.as_ref()), "12345…fghijkl");
+            }
+        });
+    }
+
     #[test]
     fn test_overlapping_folds() {
         App::test((), |app| {
@@ -577,6 +655,9 @@ mod tests {
         let iterations = env::var("ITERATIONS")
             .map(|i| i.parse().expect("invalid `ITERATIONS` variable"))
             .unwrap_or(100);
+        let operations = env::var("OPERATIONS")
+            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
+            .unwrap_or(10);
         let seed_range = if let Ok(seed) = env::var("SEED") {
             let seed = seed.parse().expect("invalid `SEED` variable");
             seed..seed + 1
@@ -596,66 +677,70 @@ mod tests {
                 });
                 let mut map = FoldMap::new(buffer.clone(), app.as_ref());
 
-                {
-                    let buffer = buffer.read(app);
-
-                    let fold_count = rng.gen_range(0..10);
-                    let mut fold_ranges: Vec<Range<usize>> = Vec::new();
-                    for _ in 0..fold_count {
-                        let end = rng.gen_range(0..buffer.len() + 1);
-                        let start = rng.gen_range(0..end + 1);
-                        fold_ranges.push(start..end);
+                for _ in 0..operations {
+                    log::info!("text: {:?}", buffer.read(app).text());
+                    {
+                        let buffer = buffer.read(app);
+
+                        let fold_count = rng.gen_range(0..=2);
+                        let mut fold_ranges: Vec<Range<usize>> = Vec::new();
+                        for _ in 0..fold_count {
+                            let end = rng.gen_range(0..buffer.len() + 1);
+                            let start = rng.gen_range(0..end + 1);
+                            fold_ranges.push(start..end);
+                        }
+                        log::info!("folding {:?}", fold_ranges);
+                        map.fold(fold_ranges.clone(), app.as_ref()).unwrap();
+                        map.check_invariants(app.as_ref());
+
+                        let mut expected_text = buffer.text();
+                        for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
+                            expected_text.replace_range(fold_range.start..fold_range.end, "…");
+                        }
+                        assert_eq!(map.text(app.as_ref()), expected_text);
+
+                        for fold_range in map.merged_fold_ranges(app.as_ref()) {
+                            let display_point =
+                                map.to_display_point(fold_range.start.to_point(buffer).unwrap());
+                            assert!(map.is_line_folded(display_point.row()));
+                        }
                     }
 
-                    map.fold(fold_ranges, app.as_ref()).unwrap();
+                    let edits = buffer.update(app, |buffer, ctx| {
+                        let start_version = buffer.version.clone();
+                        let edit_count = rng.gen_range(0..=2);
+                        buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
+                        buffer.edits_since(start_version).collect::<Vec<_>>()
+                    });
+                    log::info!("editing {:?}", edits);
+                    map.apply_edits(&edits, app.as_ref()).unwrap();
+                    map.check_invariants(app.as_ref());
 
+                    let buffer = map.buffer.read(app);
                     let mut expected_text = buffer.text();
+                    let mut expected_buffer_rows = Vec::new();
+                    let mut next_row = buffer.max_point().row;
                     for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
+                        let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
+                        let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
+                        expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
+                        next_row = fold_start.row;
+
                         expected_text.replace_range(fold_range.start..fold_range.end, "…");
                     }
+                    expected_buffer_rows.extend((0..=next_row).rev());
+                    expected_buffer_rows.reverse();
 
                     assert_eq!(map.text(app.as_ref()), expected_text);
 
-                    for fold_range in map.merged_fold_ranges(app.as_ref()) {
-                        let display_point =
-                            map.to_display_point(fold_range.start.to_point(buffer).unwrap());
-                        assert!(map.is_line_folded(display_point.row()));
+                    for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
+                        let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row();
+                        assert_eq!(
+                            map.buffer_rows(display_row).unwrap().collect::<Vec<_>>(),
+                            expected_buffer_rows[idx..],
+                        );
                     }
                 }
-
-                let edits = buffer.update(app, |buffer, ctx| {
-                    let start_version = buffer.version.clone();
-                    let edit_count = rng.gen_range(1..10);
-                    buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
-                    buffer.edits_since(start_version).collect::<Vec<_>>()
-                });
-
-                map.apply_edits(&edits, app.as_ref()).unwrap();
-
-                let buffer = map.buffer.read(app);
-                let mut expected_text = buffer.text();
-                let mut expected_buffer_rows = Vec::new();
-                let mut next_row = buffer.max_point().row;
-                for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
-                    let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
-                    let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
-                    expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
-                    next_row = fold_start.row;
-
-                    expected_text.replace_range(fold_range.start..fold_range.end, "…");
-                }
-                expected_buffer_rows.extend((0..=next_row).rev());
-                expected_buffer_rows.reverse();
-
-                assert_eq!(map.text(app.as_ref()), expected_text);
-
-                for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
-                    let display_row = map.to_display_point(Point::new(*buffer_row, 0)).row();
-                    assert_eq!(
-                        map.buffer_rows(display_row).unwrap().collect::<Vec<_>>(),
-                        expected_buffer_rows[idx..],
-                    );
-                }
             });
         }
     }
@@ -721,5 +806,13 @@ mod tests {
             }
             merged_ranges
         }
+
+        fn check_invariants(&self, app: &AppContext) {
+            assert_eq!(
+                self.transforms.summary().buffer.chars,
+                self.buffer.read(app).len(),
+                "transform tree does not match buffer's length"
+            );
+        }
     }
 }

zed/src/editor/display_map/mod.rs 🔗

@@ -34,6 +34,13 @@ impl DisplayMap {
         }
     }
 
+    pub fn folds_in_range<T>(&self, range: Range<T>, app: &AppContext) -> Result<&[Range<Anchor>]>
+    where
+        T: ToOffset,
+    {
+        self.fold_map.folds_in_range(range, app)
+    }
+
     pub fn fold<T: ToOffset>(
         &mut self,
         ranges: impl IntoIterator<Item = Range<T>>,
@@ -179,6 +186,11 @@ impl DisplayPoint {
             .to_buffer_point(self.collapse_tabs(map, bias, app)?.0))
     }
 
+    pub fn to_buffer_offset(self, map: &DisplayMap, bias: Bias, app: &AppContext) -> Result<usize> {
+        map.fold_map
+            .to_buffer_offset(self.collapse_tabs(map, bias, app)?.0, app)
+    }
+
     fn expand_tabs(mut self, map: &DisplayMap, app: &AppContext) -> Result<Self> {
         let chars = map
             .fold_map