Implement BlockSnapshot::line_len, use it in DisplayMap

Max Brunsfeld and Nathan Sobo created

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

Change summary

crates/buffer/src/rope.rs                  |  5 +++
crates/editor/src/display_map.rs           | 13 +++++---
crates/editor/src/display_map/block_map.rs | 34 +++++++++++++++++++----
crates/editor/src/display_map/wrap_map.rs  |  4 --
4 files changed, 41 insertions(+), 15 deletions(-)

Detailed changes

crates/buffer/src/rope.rs 🔗

@@ -249,6 +249,11 @@ impl Rope {
             self.summary().lines_utf16
         }
     }
+
+    pub fn line_len(&self, row: u32) -> u32 {
+        self.clip_point(Point::new(row, u32::MAX), Bias::Left)
+            .column
+    }
 }
 
 impl<'a> From<&'a str> for Rope {

crates/editor/src/display_map.rs 🔗

@@ -14,8 +14,7 @@ use sum_tree::Bias;
 use tab_map::TabMap;
 use wrap_map::WrapMap;
 
-pub use block_map::{BlockDisposition, BlockProperties, Chunks};
-pub use wrap_map::BufferRows;
+pub use block_map::{BlockDisposition, BlockProperties, BufferRows, Chunks};
 
 pub trait ToDisplayPoint {
     fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint;
@@ -174,7 +173,7 @@ impl DisplayMapSnapshot {
     }
 
     pub fn buffer_rows(&self, start_row: u32) -> BufferRows {
-        self.wraps_snapshot.buffer_rows(start_row)
+        self.blocks_snapshot.buffer_rows(start_row)
     }
 
     pub fn buffer_row_count(&self) -> u32 {
@@ -304,7 +303,11 @@ impl DisplayMapSnapshot {
     }
 
     pub fn soft_wrap_indent(&self, display_row: u32) -> Option<u32> {
-        self.wraps_snapshot.soft_wrap_indent(display_row)
+        let wrap_row = self
+            .blocks_snapshot
+            .to_wrap_point(BlockPoint::new(display_row, 0))
+            .row();
+        self.wraps_snapshot.soft_wrap_indent(wrap_row)
     }
 
     pub fn text(&self) -> String {
@@ -339,7 +342,7 @@ impl DisplayMapSnapshot {
     }
 
     pub fn line_len(&self, row: u32) -> u32 {
-        self.wraps_snapshot.line_len(row)
+        self.blocks_snapshot.line_len(row)
     }
 
     pub fn longest_row(&self) -> u32 {

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

@@ -336,7 +336,7 @@ fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32) {
 }
 
 impl BlockPoint {
-    fn new(row: u32, column: u32) -> Self {
+    pub fn new(row: u32, column: u32) -> Self {
         Self(Point::new(row, column))
     }
 }
@@ -520,7 +520,24 @@ impl BlockSnapshot {
     }
 
     pub fn max_point(&self) -> BlockPoint {
-        self.to_block_point(self.wrap_snapshot.max_point())
+        let row = self.transforms.summary().output_rows - 1;
+        BlockPoint::new(row, self.line_len(row))
+    }
+
+    pub fn line_len(&self, row: u32) -> u32 {
+        let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
+        cursor.seek(&BlockRow(row), Bias::Right, &());
+        if let Some(transform) = cursor.item() {
+            let (output_start, input_start) = cursor.start();
+            let overshoot = row - output_start.0;
+            if let Some(block) = &transform.block {
+                block.text.line_len(overshoot)
+            } else {
+                self.wrap_snapshot.line_len(input_start.0 + overshoot)
+            }
+        } else {
+            panic!("row out of range");
+        }
     }
 
     pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
@@ -819,10 +836,6 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
 }
 
 impl BlockDisposition {
-    fn is_above(&self) -> bool {
-        matches!(self, BlockDisposition::Above)
-    }
-
     fn is_below(&self) -> bool {
         matches!(self, BlockDisposition::Below)
     }
@@ -1275,6 +1288,15 @@ mod tests {
                 );
             }
 
+            for (row, line) in expected_lines.iter().enumerate() {
+                assert_eq!(
+                    blocks_snapshot.line_len(row as u32),
+                    line.len() as u32,
+                    "invalid line len for row {}",
+                    row
+                );
+            }
+
             for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
                 let wrap_point = WrapPoint::new(row, 0);
                 let block_point = blocks_snapshot.to_block_point(wrap_point);