Merge pull request #820 from zed-industries/optimize-line-len

Antonio Scandurra created

Speed up `WrapSnapshot::line_len` using the indexed transforms

Change summary

crates/editor/src/display_map/tab_map.rs  | 21 +++++++++++++
crates/editor/src/display_map/wrap_map.rs | 38 +++++++++++++++++-------
2 files changed, 46 insertions(+), 13 deletions(-)

Detailed changes

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

@@ -96,6 +96,22 @@ impl TabSnapshot {
         self.fold_snapshot.buffer_snapshot()
     }
 
+    pub fn line_len(&self, row: u32) -> u32 {
+        let max_point = self.max_point();
+        if row < max_point.row() {
+            self.chunks(
+                TabPoint::new(row, 0)..TabPoint::new(row + 1, 0),
+                false,
+                None,
+            )
+            .map(|chunk| chunk.text.len() as u32)
+            .sum::<u32>()
+                - 1
+        } else {
+            max_point.column()
+        }
+    }
+
     pub fn text_summary(&self) -> TextSummary {
         self.text_summary_for_range(TabPoint::zero()..self.max_point())
     }
@@ -517,8 +533,11 @@ mod tests {
                 actual_summary.longest_row = expected_summary.longest_row;
                 actual_summary.longest_row_chars = expected_summary.longest_row_chars;
             }
+            assert_eq!(actual_summary, expected_summary);
+        }
 
-            assert_eq!(actual_summary, expected_summary,);
+        for row in 0..=text.max_point().row {
+            assert_eq!(tabs_snapshot.line_len(row), text.line_len(row));
         }
     }
 }

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

@@ -559,11 +559,6 @@ impl WrapSnapshot {
         Patch::new(wrap_edits)
     }
 
-    pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
-        self.chunks(wrap_row..self.max_point().row() + 1, false, None)
-            .map(|h| h.text)
-    }
-
     pub fn chunks<'a>(
         &'a self,
         rows: Range<u32>,
@@ -599,16 +594,23 @@ impl WrapSnapshot {
     }
 
     pub fn line_len(&self, row: u32) -> u32 {
-        let mut len = 0;
-        for chunk in self.text_chunks(row) {
-            if let Some(newline_ix) = chunk.find('\n') {
-                len += newline_ix;
-                break;
+        let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>();
+        cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left, &());
+        if cursor
+            .item()
+            .map_or(false, |transform| transform.is_isomorphic())
+        {
+            let overshoot = row - cursor.start().0.row();
+            let tab_row = cursor.start().1.row() + overshoot;
+            let tab_line_len = self.tab_snapshot.line_len(tab_row);
+            if overshoot == 0 {
+                cursor.start().0.column() + (tab_line_len - cursor.start().1.column())
             } else {
-                len += chunk.len();
+                tab_line_len
             }
+        } else {
+            cursor.start().0.column()
         }
-        len as u32
     }
 
     pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
@@ -741,6 +743,7 @@ impl WrapSnapshot {
                 }
             }
 
+            let text = language::Rope::from(self.text().as_str());
             let input_buffer_rows = self.buffer_snapshot().buffer_rows(0).collect::<Vec<_>>();
             let mut expected_buffer_rows = Vec::new();
             let mut prev_tab_row = 0;
@@ -754,6 +757,8 @@ impl WrapSnapshot {
                     expected_buffer_rows.push(input_buffer_rows[buffer_point.row as usize]);
                     prev_tab_row = tab_point.row();
                 }
+
+                assert_eq!(self.line_len(display_row), text.line_len(display_row));
             }
 
             for start_display_row in 0..expected_buffer_rows.len() {
@@ -957,6 +962,10 @@ impl WrapPoint {
         &mut self.0.row
     }
 
+    pub fn column(self) -> u32 {
+        self.0.column
+    }
+
     pub fn column_mut(&mut self) -> &mut u32 {
         &mut self.0.column
     }
@@ -1272,6 +1281,11 @@ mod tests {
             self.text_chunks(0).collect()
         }
 
+        pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
+            self.chunks(wrap_row..self.max_point().row() + 1, false, None)
+                .map(|h| h.text)
+        }
+
         fn verify_chunks(&mut self, rng: &mut impl Rng) {
             for _ in 0..5 {
                 let mut end_row = rng.gen_range(0..=self.max_point().row());