From 8f8a92ccf07f42722808f9df139c97279962d22d Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 28 Nov 2025 09:06:59 +0100 Subject: [PATCH] editor: Reduce amount of sumtree traversals in `header_and_footer_blocks` (#43709) 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 --- 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(-) diff --git a/crates/editor/benches/display_map.rs b/crates/editor/benches/display_map.rs index 2459e7466f..c443bdba1c 100644 --- a/crates/editor/benches/display_map.rs +++ b/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); }); }, ); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 0d051507b2..7189dd9f20 100644 --- a/crates/editor/src/display_map.rs +++ b/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)) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 58dea4010c..a674404197 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/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, Block)> + 'a where R: RangeBounds, @@ -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); diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 1fe68939ad..2d37dea38a 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/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::>(()); + 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>, +} + +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, summary: MBTextSummary) { let mut did_merge = false; transforms.update_last( diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 979c398a23..73174c3018 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/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::>(()); - 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::>(()); + InlayPointCursor { + cursor, + transforms: &self.transforms, } } @@ -1162,6 +1141,51 @@ impl InlaySnapshot { } } +pub struct InlayPointCursor<'transforms> { + cursor: Cursor<'transforms, 'static, Transform, Dimensions>, + transforms: &'transforms SumTree, +} + +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, summary: MBTextSummary) { if summary.len == MultiBufferOffset(0) { return; diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index a8ffbbb177..347d773215 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/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) -> 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) { - 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!( diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index c33bf141b9..30e0e65219 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/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::>(()), + } + } + 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>, +} + +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) { let output_start = WrapPoint::new(rows.start, 0); diff --git a/crates/sum_tree/src/cursor.rs b/crates/sum_tree/src/cursor.rs index f37eae4f4b..0bb60a1300 100644 --- a/crates/sum_tree/src/cursor.rs +++ b/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>