editor: Reduce amount of sumtree traversals in `header_and_footer_blocks` (#43709)

Lukas Wirth created

Introduces new "mapping point cursors" for the different display map
layers allowing one to map multiple points in increasing order more
efficiently than using the one shot operations.

This is used in the `BlockMap::sync` for `header_and_footer_blocks`
which spends a significant time in sumtree traversal due to repeatedly
transforming points between the different layers. This effectively turns
the complexity of those operations from quadratic in the number of
excerpts to linear, as we only go through the respective sumtrees once
instead of restarting from the start over and over again.

Release Notes:

- Improved performance for editors of large multibuffers with many
different files

Change summary

crates/editor/benches/display_map.rs       |  6 
crates/editor/src/display_map.rs           | 18 +++-
crates/editor/src/display_map/block_map.rs | 55 +++++++++++----
crates/editor/src/display_map/fold_map.rs  | 32 +++++++++
crates/editor/src/display_map/inlay_map.rs | 84 +++++++++++++++--------
crates/editor/src/display_map/tab_map.rs   | 82 ++++++++++++++---------
crates/editor/src/display_map/wrap_map.rs  | 29 +++++++
crates/sum_tree/src/cursor.rs              |  4 +
8 files changed, 221 insertions(+), 89 deletions(-)

Detailed changes

crates/editor/benches/display_map.rs 🔗

@@ -45,7 +45,7 @@ fn to_tab_point_benchmark(c: &mut Criterion) {
             &snapshot,
             |bench, snapshot| {
                 bench.iter(|| {
-                    snapshot.to_tab_point(fold_point);
+                    snapshot.fold_point_to_tab_point(fold_point);
                 });
             },
         );
@@ -79,7 +79,7 @@ fn to_fold_point_benchmark(c: &mut Criterion) {
         );
 
         let (_, snapshot) = TabMap::new(fold_snapshot, NonZeroU32::new(4).unwrap());
-        let tab_point = snapshot.to_tab_point(fold_point);
+        let tab_point = snapshot.fold_point_to_tab_point(fold_point);
 
         (length, snapshot, tab_point)
     };
@@ -94,7 +94,7 @@ fn to_fold_point_benchmark(c: &mut Criterion) {
             &snapshot,
             |bench, snapshot| {
                 bench.iter(|| {
-                    snapshot.to_fold_point(tab_point, Bias::Left);
+                    snapshot.tab_point_to_fold_point(tab_point, Bias::Left);
                 });
             },
         );

crates/editor/src/display_map.rs 🔗

@@ -889,7 +889,7 @@ impl DisplaySnapshot {
     pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
         let inlay_point = self.inlay_snapshot().to_inlay_point(point);
         let fold_point = self.fold_snapshot().to_fold_point(inlay_point, bias);
-        let tab_point = self.tab_snapshot().to_tab_point(fold_point);
+        let tab_point = self.tab_snapshot().fold_point_to_tab_point(fold_point);
         let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
         let block_point = self.block_snapshot.to_block_point(wrap_point);
         DisplayPoint(block_point)
@@ -919,7 +919,10 @@ impl DisplaySnapshot {
         let block_point = point.0;
         let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
         let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
-        let fold_point = self.tab_snapshot().to_fold_point(tab_point, bias).0;
+        let fold_point = self
+            .tab_snapshot()
+            .tab_point_to_fold_point(tab_point, bias)
+            .0;
         fold_point.to_inlay_point(self.fold_snapshot())
     }
 
@@ -927,11 +930,13 @@ impl DisplaySnapshot {
         let block_point = point.0;
         let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
         let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
-        self.tab_snapshot().to_fold_point(tab_point, bias).0
+        self.tab_snapshot()
+            .tab_point_to_fold_point(tab_point, bias)
+            .0
     }
 
     pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
-        let tab_point = self.tab_snapshot().to_tab_point(fold_point);
+        let tab_point = self.tab_snapshot().fold_point_to_tab_point(fold_point);
         let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
         let block_point = self.block_snapshot.to_block_point(wrap_point);
         DisplayPoint(block_point)
@@ -1584,7 +1589,10 @@ impl DisplayPoint {
     pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> MultiBufferOffset {
         let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
         let tab_point = map.wrap_snapshot().to_tab_point(wrap_point);
-        let fold_point = map.tab_snapshot().to_fold_point(tab_point, bias).0;
+        let fold_point = map
+            .tab_snapshot()
+            .tab_point_to_fold_point(tab_point, bias)
+            .0;
         let inlay_point = fold_point.to_inlay_point(map.fold_snapshot());
         map.inlay_snapshot()
             .to_buffer_offset(map.inlay_snapshot().to_offset(inlay_point))

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

@@ -556,6 +556,11 @@ impl BlockMap {
         let mut blocks_in_edit = Vec::new();
         let mut edits = edits.into_iter().peekable();
 
+        let mut inlay_point_cursor = wrap_snapshot.inlay_point_cursor();
+        let mut tab_point_cursor = wrap_snapshot.tab_point_cursor();
+        let mut fold_point_cursor = wrap_snapshot.fold_point_cursor();
+        let mut wrap_point_cursor = wrap_snapshot.wrap_point_cursor();
+
         while let Some(edit) = edits.next() {
             let mut old_start = edit.old.start;
             let mut new_start = edit.new.start;
@@ -686,6 +691,9 @@ impl BlockMap {
             last_block_ix = end_block_ix;
 
             debug_assert!(blocks_in_edit.is_empty());
+            // + 8 is chosen arbitrarily to cover some multibuffer headers
+            blocks_in_edit
+                .reserve(end_block_ix - start_block_ix + if buffer.is_singleton() { 0 } else { 8 });
 
             blocks_in_edit.extend(
                 self.custom_blocks[start_block_ix..end_block_ix]
@@ -704,7 +712,14 @@ impl BlockMap {
             blocks_in_edit.extend(self.header_and_footer_blocks(
                 buffer,
                 (start_bound, end_bound),
-                wrap_snapshot,
+                |point, bias| {
+                    wrap_point_cursor
+                        .map(
+                            tab_point_cursor
+                                .map(fold_point_cursor.map(inlay_point_cursor.map(point), bias)),
+                        )
+                        .row()
+                },
             ));
 
             BlockMap::sort_blocks(&mut blocks_in_edit);
@@ -777,11 +792,12 @@ impl BlockMap {
         }
     }
 
+    /// Guarantees that `wrap_row_for` is called with points in increasing order.
     fn header_and_footer_blocks<'a, R, T>(
         &'a self,
         buffer: &'a multi_buffer::MultiBufferSnapshot,
         range: R,
-        wrap_snapshot: &'a WrapSnapshot,
+        mut wrap_row_for: impl 'a + FnMut(Point, Bias) -> WrapRow,
     ) -> impl Iterator<Item = (BlockPlacement<WrapRow>, Block)> + 'a
     where
         R: RangeBounds<T>,
@@ -792,9 +808,7 @@ impl BlockMap {
         std::iter::from_fn(move || {
             loop {
                 let excerpt_boundary = boundaries.next()?;
-                let wrap_row = wrap_snapshot
-                    .make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
-                    .row();
+                let wrap_row = wrap_row_for(Point::new(excerpt_boundary.row.0, 0), Bias::Left);
 
                 let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
                     (None, next) => Some(next.buffer_id),
@@ -826,16 +840,13 @@ impl BlockMap {
 
                             boundaries.next();
                         }
-
-                        let wrap_end_row = wrap_snapshot
-                            .make_wrap_point(
-                                Point::new(
-                                    last_excerpt_end_row.0,
-                                    buffer.line_len(last_excerpt_end_row),
-                                ),
-                                Bias::Right,
-                            )
-                            .row();
+                        let wrap_end_row = wrap_row_for(
+                            Point::new(
+                                last_excerpt_end_row.0,
+                                buffer.line_len(last_excerpt_end_row),
+                            ),
+                            Bias::Right,
+                        );
 
                         return Some((
                             BlockPlacement::Replace(wrap_row..=wrap_end_row),
@@ -3243,11 +3254,23 @@ mod tests {
                 ))
             }));
 
+            let mut inlay_point_cursor = wraps_snapshot.inlay_point_cursor();
+            let mut tab_point_cursor = wraps_snapshot.tab_point_cursor();
+            let mut fold_point_cursor = wraps_snapshot.fold_point_cursor();
+            let mut wrap_point_cursor = wraps_snapshot.wrap_point_cursor();
+
             // Note that this needs to be synced with the related section in BlockMap::sync
             expected_blocks.extend(block_map.header_and_footer_blocks(
                 &buffer_snapshot,
                 MultiBufferOffset(0)..,
-                &wraps_snapshot,
+                |point, bias| {
+                    wrap_point_cursor
+                        .map(
+                            tab_point_cursor
+                                .map(fold_point_cursor.map(inlay_point_cursor.map(point), bias)),
+                        )
+                        .row()
+                },
             ));
 
             BlockMap::sort_blocks(&mut expected_blocks);

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

@@ -734,6 +734,13 @@ impl FoldSnapshot {
         }
     }
 
+    pub fn fold_point_cursor(&self) -> FoldPointCursor<'_> {
+        let cursor = self
+            .transforms
+            .cursor::<Dimensions<InlayPoint, FoldPoint>>(());
+        FoldPointCursor { cursor }
+    }
+
     pub fn len(&self) -> FoldOffset {
         FoldOffset(self.transforms.summary().output.len)
     }
@@ -927,6 +934,31 @@ impl FoldSnapshot {
     }
 }
 
+pub struct FoldPointCursor<'transforms> {
+    cursor: Cursor<'transforms, 'static, Transform, Dimensions<InlayPoint, FoldPoint>>,
+}
+
+impl FoldPointCursor<'_> {
+    pub fn map(&mut self, point: InlayPoint, bias: Bias) -> FoldPoint {
+        let cursor = &mut self.cursor;
+        if cursor.did_seek() {
+            cursor.seek_forward(&point, Bias::Right);
+        } else {
+            cursor.seek(&point, Bias::Right);
+        }
+        if cursor.item().is_some_and(|t| t.is_fold()) {
+            if bias == Bias::Left || point == cursor.start().0 {
+                cursor.start().1
+            } else {
+                cursor.end().1
+            }
+        } else {
+            let overshoot = point.0 - cursor.start().0.0;
+            FoldPoint(cmp::min(cursor.start().1.0 + overshoot, cursor.end().1.0))
+        }
+    }
+}
+
 fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: MBTextSummary) {
     let mut did_merge = false;
     transforms.update_last(

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

@@ -879,37 +879,16 @@ impl InlaySnapshot {
             }
         }
     }
+
     pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
-        let mut cursor = self.transforms.cursor::<Dimensions<Point, InlayPoint>>(());
-        cursor.seek(&point, Bias::Left);
-        loop {
-            match cursor.item() {
-                Some(Transform::Isomorphic(_)) => {
-                    if point == cursor.end().0 {
-                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
-                            if inlay.position.bias() == Bias::Right {
-                                break;
-                            } else {
-                                cursor.next();
-                            }
-                        }
-                        return cursor.end().1;
-                    } else {
-                        let overshoot = point - cursor.start().0;
-                        return InlayPoint(cursor.start().1.0 + overshoot);
-                    }
-                }
-                Some(Transform::Inlay(inlay)) => {
-                    if inlay.position.bias() == Bias::Left {
-                        cursor.next();
-                    } else {
-                        return cursor.start().1;
-                    }
-                }
-                None => {
-                    return self.max_point();
-                }
-            }
+        self.inlay_point_cursor().map(point)
+    }
+
+    pub fn inlay_point_cursor(&self) -> InlayPointCursor<'_> {
+        let cursor = self.transforms.cursor::<Dimensions<Point, InlayPoint>>(());
+        InlayPointCursor {
+            cursor,
+            transforms: &self.transforms,
         }
     }
 
@@ -1162,6 +1141,51 @@ impl InlaySnapshot {
     }
 }
 
+pub struct InlayPointCursor<'transforms> {
+    cursor: Cursor<'transforms, 'static, Transform, Dimensions<Point, InlayPoint>>,
+    transforms: &'transforms SumTree<Transform>,
+}
+
+impl InlayPointCursor<'_> {
+    pub fn map(&mut self, point: Point) -> InlayPoint {
+        let cursor = &mut self.cursor;
+        if cursor.did_seek() {
+            cursor.seek_forward(&point, Bias::Left);
+        } else {
+            cursor.seek(&point, Bias::Left);
+        }
+        loop {
+            match cursor.item() {
+                Some(Transform::Isomorphic(_)) => {
+                    if point == cursor.end().0 {
+                        while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
+                            if inlay.position.bias() == Bias::Right {
+                                break;
+                            } else {
+                                cursor.next();
+                            }
+                        }
+                        return cursor.end().1;
+                    } else {
+                        let overshoot = point - cursor.start().0;
+                        return InlayPoint(cursor.start().1.0 + overshoot);
+                    }
+                }
+                Some(Transform::Inlay(inlay)) => {
+                    if inlay.position.bias() == Bias::Left {
+                        cursor.next();
+                    } else {
+                        return cursor.start().1;
+                    }
+                }
+                None => {
+                    return InlayPoint(self.transforms.summary().output.lines);
+                }
+            }
+        }
+    }
+}
+
 fn push_isomorphic(sum_tree: &mut SumTree<Transform>, summary: MBTextSummary) {
     if summary.len == MultiBufferOffset(0) {
         return;

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

@@ -137,10 +137,10 @@ impl TabMap {
                         let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot);
                         let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot);
                         TabEdit {
-                            old: old_snapshot.to_tab_point(old_start)
-                                ..old_snapshot.to_tab_point(old_end),
-                            new: new_snapshot.to_tab_point(new_start)
-                                ..new_snapshot.to_tab_point(new_end),
+                            old: old_snapshot.fold_point_to_tab_point(old_start)
+                                ..old_snapshot.fold_point_to_tab_point(old_end),
+                            new: new_snapshot.fold_point_to_tab_point(new_start)
+                                ..new_snapshot.fold_point_to_tab_point(new_end),
                         }
                     })
                     .collect()
@@ -183,7 +183,7 @@ impl TabSnapshot {
     pub fn line_len(&self, row: u32) -> u32 {
         let max_point = self.max_point();
         if row < max_point.row() {
-            self.to_tab_point(FoldPoint::new(row, self.fold_snapshot.line_len(row)))
+            self.fold_point_to_tab_point(FoldPoint::new(row, self.fold_snapshot.line_len(row)))
                 .0
                 .column
         } else {
@@ -196,8 +196,8 @@ impl TabSnapshot {
     }
 
     pub fn text_summary_for_range(&self, range: Range<TabPoint>) -> TextSummary {
-        let input_start = self.to_fold_point(range.start, Bias::Left).0;
-        let input_end = self.to_fold_point(range.end, Bias::Right).0;
+        let input_start = self.tab_point_to_fold_point(range.start, Bias::Left).0;
+        let input_end = self.tab_point_to_fold_point(range.end, Bias::Right).0;
         let input_summary = self
             .fold_snapshot
             .text_summary_for_range(input_start..input_end);
@@ -241,11 +241,11 @@ impl TabSnapshot {
         highlights: Highlights<'a>,
     ) -> TabChunks<'a> {
         let (input_start, expanded_char_column, to_next_stop) =
-            self.to_fold_point(range.start, Bias::Left);
+            self.tab_point_to_fold_point(range.start, Bias::Left);
         let input_column = input_start.column();
         let input_start = input_start.to_offset(&self.fold_snapshot);
         let input_end = self
-            .to_fold_point(range.end, Bias::Right)
+            .tab_point_to_fold_point(range.end, Bias::Right)
             .0
             .to_offset(&self.fold_snapshot);
         let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 {
@@ -292,24 +292,28 @@ impl TabSnapshot {
     }
 
     pub fn max_point(&self) -> TabPoint {
-        self.to_tab_point(self.fold_snapshot.max_point())
+        self.fold_point_to_tab_point(self.fold_snapshot.max_point())
     }
 
     pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint {
-        self.to_tab_point(
+        self.fold_point_to_tab_point(
             self.fold_snapshot
-                .clip_point(self.to_fold_point(point, bias).0, bias),
+                .clip_point(self.tab_point_to_fold_point(point, bias).0, bias),
         )
     }
 
-    pub fn to_tab_point(&self, input: FoldPoint) -> TabPoint {
+    pub fn fold_point_to_tab_point(&self, input: FoldPoint) -> TabPoint {
         let chunks = self.fold_snapshot.chunks_at(FoldPoint::new(input.row(), 0));
         let tab_cursor = TabStopCursor::new(chunks);
         let expanded = self.expand_tabs(tab_cursor, input.column());
         TabPoint::new(input.row(), expanded)
     }
 
-    pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, u32, u32) {
+    pub fn tab_point_cursor(&self) -> TabPointCursor<'_> {
+        TabPointCursor { this: self }
+    }
+
+    pub fn tab_point_to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, u32, u32) {
         let chunks = self
             .fold_snapshot
             .chunks_at(FoldPoint::new(output.row(), 0));
@@ -326,14 +330,14 @@ impl TabSnapshot {
         )
     }
 
-    pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
+    pub fn point_to_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
         let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point);
         let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
-        self.to_tab_point(fold_point)
+        self.fold_point_to_tab_point(fold_point)
     }
 
-    pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
-        let fold_point = self.to_fold_point(point, bias).0;
+    pub fn tab_point_to_point(&self, point: TabPoint, bias: Bias) -> Point {
+        let fold_point = self.tab_point_to_fold_point(point, bias).0;
         let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
         self.fold_snapshot
             .inlay_snapshot
@@ -432,6 +436,17 @@ impl TabSnapshot {
     }
 }
 
+// todo(lw): Implement TabPointCursor properly
+pub struct TabPointCursor<'this> {
+    this: &'this TabSnapshot,
+}
+
+impl TabPointCursor<'_> {
+    pub fn map(&mut self, point: FoldPoint) -> TabPoint {
+        self.this.fold_point_to_tab_point(point)
+    }
+}
+
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 pub struct TabPoint(pub Point);
 
@@ -527,13 +542,14 @@ pub struct TabChunks<'a> {
 
 impl TabChunks<'_> {
     pub(crate) fn seek(&mut self, range: Range<TabPoint>) {
-        let (input_start, expanded_char_column, to_next_stop) =
-            self.snapshot.to_fold_point(range.start, Bias::Left);
+        let (input_start, expanded_char_column, to_next_stop) = self
+            .snapshot
+            .tab_point_to_fold_point(range.start, Bias::Left);
         let input_column = input_start.column();
         let input_start = input_start.to_offset(&self.snapshot.fold_snapshot);
         let input_end = self
             .snapshot
-            .to_fold_point(range.end, Bias::Right)
+            .tab_point_to_fold_point(range.end, Bias::Right)
             .0
             .to_offset(&self.snapshot.fold_snapshot);
         let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 {
@@ -804,23 +820,23 @@ mod tests {
 
             assert_eq!(
                 tab_snapshot.expected_to_fold_point(range.start, Bias::Left),
-                tab_snapshot.to_fold_point(range.start, Bias::Left),
+                tab_snapshot.tab_point_to_fold_point(range.start, Bias::Left),
                 "Failed with tab_point at column {ix}"
             );
             assert_eq!(
                 tab_snapshot.expected_to_fold_point(range.start, Bias::Right),
-                tab_snapshot.to_fold_point(range.start, Bias::Right),
+                tab_snapshot.tab_point_to_fold_point(range.start, Bias::Right),
                 "Failed with tab_point at column {ix}"
             );
 
             assert_eq!(
                 tab_snapshot.expected_to_fold_point(range.end, Bias::Left),
-                tab_snapshot.to_fold_point(range.end, Bias::Left),
+                tab_snapshot.tab_point_to_fold_point(range.end, Bias::Left),
                 "Failed with tab_point at column {ix}"
             );
             assert_eq!(
                 tab_snapshot.expected_to_fold_point(range.end, Bias::Right),
-                tab_snapshot.to_fold_point(range.end, Bias::Right),
+                tab_snapshot.tab_point_to_fold_point(range.end, Bias::Right),
                 "Failed with tab_point at column {ix}"
             );
         }
@@ -840,7 +856,7 @@ mod tests {
 
         // This should panic with the expected vs actual mismatch
         let tab_point = TabPoint::new(0, 9);
-        let result = tab_snapshot.to_fold_point(tab_point, Bias::Left);
+        let result = tab_snapshot.tab_point_to_fold_point(tab_point, Bias::Left);
         let expected = tab_snapshot.expected_to_fold_point(tab_point, Bias::Left);
 
         assert_eq!(result, expected);
@@ -884,26 +900,26 @@ mod tests {
 
             assert_eq!(
                 tab_snapshot.expected_to_fold_point(range.start, Bias::Left),
-                tab_snapshot.to_fold_point(range.start, Bias::Left),
+                tab_snapshot.tab_point_to_fold_point(range.start, Bias::Left),
                 "Failed with input: {}, with idx: {ix}",
                 input
             );
             assert_eq!(
                 tab_snapshot.expected_to_fold_point(range.start, Bias::Right),
-                tab_snapshot.to_fold_point(range.start, Bias::Right),
+                tab_snapshot.tab_point_to_fold_point(range.start, Bias::Right),
                 "Failed with input: {}, with idx: {ix}",
                 input
             );
 
             assert_eq!(
                 tab_snapshot.expected_to_fold_point(range.end, Bias::Left),
-                tab_snapshot.to_fold_point(range.end, Bias::Left),
+                tab_snapshot.tab_point_to_fold_point(range.end, Bias::Left),
                 "Failed with input: {}, with idx: {ix}",
                 input
             );
             assert_eq!(
                 tab_snapshot.expected_to_fold_point(range.end, Bias::Right),
-                tab_snapshot.to_fold_point(range.end, Bias::Right),
+                tab_snapshot.tab_point_to_fold_point(range.end, Bias::Right),
                 "Failed with input: {}, with idx: {ix}",
                 input
             );
@@ -943,13 +959,13 @@ mod tests {
                 let input_point = Point::new(0, ix as u32);
                 let output_point = Point::new(0, output.find(c).unwrap() as u32);
                 assert_eq!(
-                    tab_snapshot.to_tab_point(FoldPoint(input_point)),
+                    tab_snapshot.fold_point_to_tab_point(FoldPoint(input_point)),
                     TabPoint(output_point),
                     "to_tab_point({input_point:?})"
                 );
                 assert_eq!(
                     tab_snapshot
-                        .to_fold_point(TabPoint(output_point), Bias::Left)
+                        .tab_point_to_fold_point(TabPoint(output_point), Bias::Left)
                         .0,
                     FoldPoint(input_point),
                     "to_fold_point({output_point:?})"
@@ -1138,7 +1154,7 @@ mod tests {
             let column = rng.random_range(0..=max_column + 10);
             let fold_point = FoldPoint::new(row, column);
 
-            let actual = tab_snapshot.to_tab_point(fold_point);
+            let actual = tab_snapshot.fold_point_to_tab_point(fold_point);
             let expected = tab_snapshot.expected_to_tab_point(fold_point);
 
             assert_eq!(

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

@@ -778,11 +778,12 @@ impl WrapSnapshot {
     }
 
     pub fn to_point(&self, point: WrapPoint, bias: Bias) -> Point {
-        self.tab_snapshot.to_point(self.to_tab_point(point), bias)
+        self.tab_snapshot
+            .tab_point_to_point(self.to_tab_point(point), bias)
     }
 
     pub fn make_wrap_point(&self, point: Point, bias: Bias) -> WrapPoint {
-        self.tab_point_to_wrap_point(self.tab_snapshot.make_tab_point(point, bias))
+        self.tab_point_to_wrap_point(self.tab_snapshot.point_to_tab_point(point, bias))
     }
 
     pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint {
@@ -792,6 +793,14 @@ impl WrapSnapshot {
         WrapPoint(start.1.0 + (point.0 - start.0.0))
     }
 
+    pub fn wrap_point_cursor(&self) -> WrapPointCursor<'_> {
+        WrapPointCursor {
+            cursor: self
+                .transforms
+                .cursor::<Dimensions<TabPoint, WrapPoint>>(()),
+        }
+    }
+
     pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint {
         if bias == Bias::Left {
             let (start, _, item) = self
@@ -913,6 +922,22 @@ impl WrapSnapshot {
     }
 }
 
+pub struct WrapPointCursor<'transforms> {
+    cursor: Cursor<'transforms, 'static, Transform, Dimensions<TabPoint, WrapPoint>>,
+}
+
+impl WrapPointCursor<'_> {
+    pub fn map(&mut self, point: TabPoint) -> WrapPoint {
+        let cursor = &mut self.cursor;
+        if cursor.did_seek() {
+            cursor.seek_forward(&point, Bias::Right);
+        } else {
+            cursor.seek(&point, Bias::Right);
+        }
+        WrapPoint(cursor.start().1.0 + (point.0 - cursor.start().0.0))
+    }
+}
+
 impl WrapChunks<'_> {
     pub(crate) fn seek(&mut self, rows: Range<WrapRow>) {
         let output_start = WrapPoint::new(rows.start, 0);

crates/sum_tree/src/cursor.rs 🔗

@@ -381,6 +381,10 @@ where
             "Must call `seek`, `next` or `prev` before calling this method"
         );
     }
+
+    pub fn did_seek(&self) -> bool {
+        self.did_seek
+    }
 }
 
 impl<'a, 'b, T, D> Cursor<'a, 'b, T, D>