Integrate MultiBuffer::buffer_rows into the display map

Max Brunsfeld created

Change summary

crates/editor/src/display_map/block_map.rs |   7 
crates/editor/src/display_map/fold_map.rs  |  88 +++++++++++++++-----
crates/editor/src/display_map/wrap_map.rs  |  15 +--
crates/editor/src/multi_buffer.rs          | 103 ++++++++++++++++-------
4 files changed, 146 insertions(+), 67 deletions(-)

Detailed changes

crates/editor/src/display_map/block_map.rs 🔗

@@ -1263,6 +1263,7 @@ mod tests {
                 .sort_unstable_by_key(|(id, block)| (block.position.row, block.disposition, *id));
             let mut sorted_blocks = sorted_blocks.into_iter().peekable();
 
+            let input_buffer_rows = buffer_snapshot.buffer_rows(0).collect::<Vec<_>>();
             let mut expected_buffer_rows = Vec::new();
             let mut expected_text = String::new();
             let input_text = wraps_snapshot.text();
@@ -1272,9 +1273,9 @@ mod tests {
                     expected_text.push('\n');
                 }
 
-                let buffer_row = wraps_snapshot
+                let buffer_row = input_buffer_rows[wraps_snapshot
                     .to_point(WrapPoint::new(row, 0), Bias::Left)
-                    .row;
+                    .row as usize];
 
                 while let Some((_, block)) = sorted_blocks.peek() {
                     if block.position.row == row && block.disposition == BlockDisposition::Above {
@@ -1290,7 +1291,7 @@ mod tests {
                 }
 
                 let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
-                expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) });
+                expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
                 expected_text.push_str(input_line);
 
                 while let Some((_, block)) = sorted_blocks.peek() {

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

@@ -1,4 +1,7 @@
-use crate::{Anchor, AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot, ToOffset};
+use crate::{
+    multi_buffer::MultiBufferRows, Anchor, AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot,
+    ToOffset,
+};
 use language::{Chunk, Edit, Point, PointUtf16, TextSummary};
 use parking_lot::Mutex;
 use std::{
@@ -563,9 +566,18 @@ impl FoldSnapshot {
         }
 
         let fold_point = FoldPoint::new(start_row, 0);
-        let mut cursor = self.transforms.cursor();
+        let mut cursor = self.transforms.cursor::<(FoldPoint, Point)>();
         cursor.seek(&fold_point, Bias::Left, &());
-        FoldBufferRows { fold_point, cursor }
+
+        let overshoot = fold_point.0 - cursor.start().0 .0;
+        let buffer_point = cursor.start().1 + overshoot;
+        let input_buffer_rows = self.buffer_snapshot.buffer_rows(buffer_point.row);
+
+        FoldBufferRows {
+            fold_point,
+            input_buffer_rows,
+            cursor,
+        }
     }
 
     pub fn max_point(&self) -> FoldPoint {
@@ -897,26 +909,30 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
 
 pub struct FoldBufferRows<'a> {
     cursor: Cursor<'a, Transform, (FoldPoint, Point)>,
+    input_buffer_rows: MultiBufferRows<'a>,
     fold_point: FoldPoint,
 }
 
 impl<'a> Iterator for FoldBufferRows<'a> {
-    type Item = u32;
+    type Item = Option<u32>;
 
     fn next(&mut self) -> Option<Self::Item> {
+        let mut traversed_fold = false;
         while self.fold_point > self.cursor.end(&()).0 {
             self.cursor.next(&());
+            traversed_fold = true;
             if self.cursor.item().is_none() {
-                // TODO: Return a bool from next?
                 break;
             }
         }
 
         if self.cursor.item().is_some() {
-            let overshoot = self.fold_point.0 - self.cursor.start().0 .0;
-            let buffer_point = self.cursor.start().1 + overshoot;
+            if traversed_fold {
+                self.input_buffer_rows.seek(self.cursor.start().1.row);
+                self.input_buffer_rows.next();
+            }
             *self.fold_point.row_mut() += 1;
-            Some(buffer_point.row)
+            self.input_buffer_rows.next()
         } else {
             None
         }
@@ -1282,20 +1298,38 @@ mod tests {
             snapshot_edits.push((snapshot.clone(), edits));
 
             let mut expected_text: String = buffer_snapshot.text().to_string();
-            let mut expected_buffer_rows = Vec::new();
-            let mut next_row = buffer_snapshot.max_point().row;
             for fold_range in map.merged_fold_ranges().into_iter().rev() {
-                let fold_start = buffer_snapshot.offset_to_point(fold_range.start);
-                let fold_end = buffer_snapshot.offset_to_point(fold_range.end);
-                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!(snapshot.text(), expected_text);
+            log::info!(
+                "fold text {:?} ({} lines)",
+                expected_text,
+                expected_text.matches('\n').count() + 1
+            );
+
+            let mut prev_row = 0;
+            let mut expected_buffer_rows = Vec::new();
+            for fold_range in map.merged_fold_ranges().into_iter() {
+                let fold_start = buffer_snapshot.offset_to_point(fold_range.start).row;
+                let fold_end = buffer_snapshot.offset_to_point(fold_range.end).row;
+                expected_buffer_rows.extend(
+                    buffer_snapshot
+                        .buffer_rows(prev_row)
+                        .take((1 + fold_start - prev_row) as usize),
+                );
+                prev_row = 1 + fold_end;
+            }
+            expected_buffer_rows.extend(buffer_snapshot.buffer_rows(prev_row));
+
+            assert_eq!(
+                expected_buffer_rows.len(),
+                expected_text.matches('\n').count() + 1,
+                "wrong expected buffer rows {:?}. text: {:?}",
+                expected_buffer_rows,
+                expected_text
+            );
 
             for (output_row, line) in expected_text.lines().enumerate() {
                 let line_len = snapshot.line_len(output_row as u32);
@@ -1373,14 +1407,19 @@ mod tests {
                 );
             }
 
-            for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
-                let fold_row = Point::new(*buffer_row, 0)
-                    .to_fold_point(&snapshot, Right)
+            let mut fold_row = 0;
+            while fold_row < expected_buffer_rows.len() as u32 {
+                fold_row = snapshot
+                    .clip_point(FoldPoint::new(fold_row, 0), Bias::Right)
                     .row();
+                eprintln!("fold_row: {} of {}", fold_row, expected_buffer_rows.len());
                 assert_eq!(
                     snapshot.buffer_rows(fold_row).collect::<Vec<_>>(),
-                    expected_buffer_rows[idx..],
+                    expected_buffer_rows[(fold_row as usize)..],
+                    "wrong buffer rows starting at fold row {}",
+                    fold_row,
                 );
+                fold_row += 1;
             }
 
             for fold_range in map.merged_fold_ranges() {
@@ -1470,8 +1509,11 @@ mod tests {
 
         let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]);
         assert_eq!(snapshot.text(), "aa…cccc\nd…eeeee\nffffff\n");
-        assert_eq!(snapshot.buffer_rows(0).collect::<Vec<_>>(), [0, 3, 5, 6]);
-        assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [6]);
+        assert_eq!(
+            snapshot.buffer_rows(0).collect::<Vec<_>>(),
+            [Some(0), Some(3), Some(5), Some(6)]
+        );
+        assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
     }
 
     impl FoldMap {

crates/editor/src/display_map/wrap_map.rs 🔗

@@ -64,7 +64,7 @@ pub struct WrapChunks<'a> {
 
 pub struct WrapBufferRows<'a> {
     input_buffer_rows: fold_map::FoldBufferRows<'a>,
-    input_buffer_row: u32,
+    input_buffer_row: Option<u32>,
     output_row: u32,
     soft_wrapped: bool,
     max_output_row: u32,
@@ -751,22 +751,19 @@ impl WrapSnapshot {
                 }
             }
 
+            let input_buffer_rows = self.buffer_snapshot().buffer_rows(0).collect::<Vec<_>>();
             let mut expected_buffer_rows = Vec::new();
-            let mut buffer_row = 0;
             let mut prev_tab_row = 0;
             for display_row in 0..=self.max_point().row() {
                 let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0));
-                let soft_wrapped;
-                if tab_point.row() == prev_tab_row {
-                    soft_wrapped = display_row != 0;
+                if tab_point.row() == prev_tab_row && display_row != 0 {
+                    expected_buffer_rows.push(None);
                 } else {
                     let fold_point = self.tab_snapshot.to_fold_point(tab_point, Bias::Left).0;
                     let buffer_point = fold_point.to_buffer_point(&self.tab_snapshot.fold_snapshot);
-                    buffer_row = buffer_point.row;
+                    expected_buffer_rows.push(input_buffer_rows[buffer_point.row as usize]);
                     prev_tab_row = tab_point.row();
-                    soft_wrapped = false;
                 }
-                expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) });
             }
 
             for start_display_row in 0..expected_buffer_rows.len() {
@@ -866,7 +863,7 @@ impl<'a> Iterator for WrapBufferRows<'a> {
             self.soft_wrapped = true;
         }
 
-        Some(if soft_wrapped { None } else { Some(buffer_row) })
+        Some(if soft_wrapped { None } else { buffer_row })
     }
 }
 

crates/editor/src/multi_buffer.rs 🔗

@@ -1059,29 +1059,13 @@ impl MultiBufferSnapshot {
     }
 
     pub fn buffer_rows<'a>(&'a self, start_row: u32) -> MultiBufferRows<'a> {
-        let mut excerpts = self.excerpts.cursor::<Point>();
-        excerpts.seek(&Point::new(start_row, 0), Bias::Right, &());
-        if excerpts.item().is_none() {
-            excerpts.prev(&());
-        }
-
-        let mut header_height = 0;
-        let mut buffer_row_range = 0..0;
-        if let Some(excerpt) = excerpts.item() {
-            let overshoot = start_row - excerpts.start().row;
-            let excerpt_start = excerpt.range.start.to_point(&excerpt.buffer).row;
-            let excerpt_header_height = excerpt.header_height as u32;
-            header_height = excerpt_header_height.saturating_sub(overshoot);
-            buffer_row_range.start =
-                excerpt_start + overshoot.saturating_sub(excerpt_header_height);
-            buffer_row_range.end =
-                excerpt_start + excerpt.text_summary.lines.row + 1 - excerpt_header_height;
-        }
-        MultiBufferRows {
-            header_height,
-            buffer_row_range,
-            excerpts,
-        }
+        let mut result = MultiBufferRows {
+            header_height: 0,
+            buffer_row_range: 0..0,
+            excerpts: self.excerpts.cursor(),
+        };
+        result.seek(start_row);
+        result
     }
 
     pub fn chunks<'a, T: ToOffset>(
@@ -1853,6 +1837,36 @@ impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a ExcerptId> {
     }
 }
 
+impl<'a> MultiBufferRows<'a> {
+    pub fn seek(&mut self, row: u32) {
+        self.header_height = 0;
+        self.buffer_row_range = 0..0;
+
+        self.excerpts
+            .seek_forward(&Point::new(row, 0), Bias::Right, &());
+        if self.excerpts.item().is_none() {
+            self.excerpts.prev(&());
+
+            if self.excerpts.item().is_none() && row == 0 {
+                self.buffer_row_range = 0..1;
+                return;
+            }
+        }
+
+        if let Some(excerpt) = self.excerpts.item() {
+            let overshoot = row - self.excerpts.start().row;
+            let excerpt_start = excerpt.range.start.to_point(&excerpt.buffer).row;
+            let excerpt_header_height = excerpt.header_height as u32;
+
+            self.header_height = excerpt_header_height.saturating_sub(overshoot);
+            self.buffer_row_range.start =
+                excerpt_start + overshoot.saturating_sub(excerpt_header_height);
+            self.buffer_row_range.end =
+                excerpt_start + excerpt.text_summary.lines.row + 1 - excerpt_header_height;
+        }
+    }
+}
+
 impl<'a> Iterator for MultiBufferRows<'a> {
     type Item = Option<u32>;
 
@@ -1867,16 +1881,14 @@ impl<'a> Iterator for MultiBufferRows<'a> {
                 self.buffer_row_range.start += 1;
                 return Some(row);
             }
+            self.excerpts.item()?;
             self.excerpts.next(&());
-            if let Some(excerpt) = self.excerpts.item() {
-                self.header_height = excerpt.header_height as u32;
-                self.buffer_row_range.start = excerpt.range.start.to_point(&excerpt.buffer).row;
-                self.buffer_row_range.end =
-                    self.buffer_row_range.start + excerpt.text_summary.lines.row + 1
-                        - self.header_height;
-            } else {
-                return None;
-            }
+            let excerpt = self.excerpts.item()?;
+            self.header_height = excerpt.header_height as u32;
+            self.buffer_row_range.start = excerpt.range.start.to_point(&excerpt.buffer).row;
+            self.buffer_row_range.end =
+                self.buffer_row_range.start + excerpt.text_summary.lines.row + 1
+                    - self.header_height;
         }
     }
 }
@@ -2179,6 +2191,23 @@ mod tests {
                 Some(3)
             ]
         );
+        assert_eq!(
+            snapshot.buffer_rows(2).collect::<Vec<_>>(),
+            &[
+                Some(1),
+                Some(2),
+                None,
+                Some(3),
+                Some(4),
+                None,
+                None,
+                None,
+                Some(3)
+            ]
+        );
+        assert_eq!(snapshot.buffer_rows(10).collect::<Vec<_>>(), &[Some(3)]);
+        assert_eq!(snapshot.buffer_rows(11).collect::<Vec<_>>(), &[]);
+        assert_eq!(snapshot.buffer_rows(12).collect::<Vec<_>>(), &[]);
 
         {
             let snapshot = multibuffer.read(cx).read(cx);
@@ -2283,6 +2312,16 @@ mod tests {
         );
     }
 
+    #[gpui::test]
+    fn test_empty_excerpt_buffer(cx: &mut MutableAppContext) {
+        let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
+
+        let snapshot = multibuffer.read(cx).snapshot(cx);
+        assert_eq!(snapshot.text(), "");
+        assert_eq!(snapshot.buffer_rows(0).collect::<Vec<_>>(), &[Some(0)]);
+        assert_eq!(snapshot.buffer_rows(1).collect::<Vec<_>>(), &[]);
+    }
+
     #[gpui::test]
     fn test_singleton_multibuffer_anchors(cx: &mut MutableAppContext) {
         let buffer = cx.add_model(|cx| Buffer::new(0, "abcd", cx));