From d8f4293ac3f5807703016cdba575ed2612b335e0 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 20 Oct 2025 19:20:09 +0200 Subject: [PATCH] sum_tree: Implement recursive `Sumtree::find`, use it over `Cursor::seek` if possible (#40700) Reduces peak stack usage in these functions and should generally be a bit performant. Display map benchmark results ``` To tab point/to_tab_point/1024 time: [531.40 ns 532.10 ns 532.97 ns] change: [-2.1824% -2.0054% -1.8125%] (p = 0.00 < 0.05) Performance has improved. Found 1 outliers among 100 measurements (1.00%) 1 (1.00%) high severe To fold point/to_fold_point/1024 time: [530.81 ns 531.30 ns 531.80 ns] change: [-2.0295% -1.9054% -1.7716%] (p = 0.00 < 0.05) Performance has improved. Found 3 outliers among 100 measurements (3.00%) 2 (2.00%) high mild 1 (1.00%) high severe ``` Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/editor/src/display_map/block_map.rs | 60 +++++---- crates/editor/src/display_map/fold_map.rs | 87 ++++++------ crates/editor/src/display_map/inlay_map.rs | 56 ++++---- crates/editor/src/display_map/wrap_map.rs | 63 ++++----- crates/gpui/src/elements/list.rs | 27 ++-- crates/multi_buffer/src/multi_buffer.rs | 5 +- .../notifications/src/notification_store.rs | 14 +- crates/rope/src/rope.rs | 38 +++--- crates/sum_tree/src/cursor.rs | 4 +- crates/sum_tree/src/sum_tree.rs | 126 +++++++++++++++++- crates/sum_tree/src/tree_map.rs | 7 +- crates/text/src/anchor.rs | 13 +- crates/text/src/text.rs | 38 +++--- 13 files changed, 332 insertions(+), 206 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index c954e1ba1b487c1c33187e895b7897f8ed67f94e..4b8a48ac2d9d2b35fbd15724c06032056e05ca67 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1521,10 +1521,11 @@ impl BlockSnapshot { } pub(super) fn line_len(&self, row: BlockRow) -> u32 { - let mut cursor = self.transforms.cursor::>(()); - cursor.seek(&BlockRow(row.0), Bias::Right); - if let Some(transform) = cursor.item() { - let Dimensions(output_start, input_start, _) = cursor.start(); + let (start, _, item) = + self.transforms + .find::, _>((), &row, Bias::Right); + if let Some(transform) = item { + let Dimensions(output_start, input_start, _) = start; let overshoot = row.0 - output_start.0; if transform.block.is_some() { 0 @@ -1539,15 +1540,13 @@ impl BlockSnapshot { } pub(super) fn is_block_line(&self, row: BlockRow) -> bool { - let mut cursor = self.transforms.cursor::>(()); - cursor.seek(&row, Bias::Right); - cursor.item().is_some_and(|t| t.block.is_some()) + let (_, _, item) = self.transforms.find::((), &row, Bias::Right); + item.is_some_and(|t| t.block.is_some()) } pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool { - let mut cursor = self.transforms.cursor::>(()); - cursor.seek(&row, Bias::Right); - let Some(transform) = cursor.item() else { + let (_, _, item) = self.transforms.find::((), &row, Bias::Right); + let Some(transform) = item else { return false; }; matches!(transform.block, Some(Block::FoldedBuffer { .. })) @@ -1557,9 +1556,10 @@ impl BlockSnapshot { let wrap_point = self .wrap_snapshot .make_wrap_point(Point::new(row.0, 0), Bias::Left); - let mut cursor = self.transforms.cursor::>(()); - cursor.seek(&WrapRow(wrap_point.row()), Bias::Right); - cursor.item().is_some_and(|transform| { + let (_, _, item) = + self.transforms + .find::((), &WrapRow(wrap_point.row()), Bias::Right); + item.is_some_and(|transform| { transform .block .as_ref() @@ -1627,13 +1627,16 @@ impl BlockSnapshot { } pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint { - let mut cursor = self.transforms.cursor::>(()); - cursor.seek(&WrapRow(wrap_point.row()), Bias::Right); - if let Some(transform) = cursor.item() { + let (start, _, item) = self.transforms.find::, _>( + (), + &WrapRow(wrap_point.row()), + Bias::Right, + ); + if let Some(transform) = item { if transform.block.is_some() { - BlockPoint::new(cursor.start().1.0, 0) + BlockPoint::new(start.1.0, 0) } else { - let Dimensions(input_start_row, output_start_row, _) = cursor.start(); + let Dimensions(input_start_row, output_start_row, _) = start; let input_start = Point::new(input_start_row.0, 0); let output_start = Point::new(output_start_row.0, 0); let input_overshoot = wrap_point.0 - input_start; @@ -1645,26 +1648,29 @@ impl BlockSnapshot { } pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint { - let mut cursor = self.transforms.cursor::>(()); - cursor.seek(&BlockRow(block_point.row), Bias::Right); - if let Some(transform) = cursor.item() { + let (start, end, item) = self.transforms.find::, _>( + (), + &BlockRow(block_point.row), + Bias::Right, + ); + if let Some(transform) = item { match transform.block.as_ref() { Some(block) => { if block.place_below() { - let wrap_row = cursor.start().1.0 - 1; + let wrap_row = start.1.0 - 1; WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row)) } else if block.place_above() { - WrapPoint::new(cursor.start().1.0, 0) + WrapPoint::new(start.1.0, 0) } else if bias == Bias::Left { - WrapPoint::new(cursor.start().1.0, 0) + WrapPoint::new(start.1.0, 0) } else { - let wrap_row = cursor.end().1.0 - 1; + let wrap_row = end.1.0 - 1; WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row)) } } None => { - let overshoot = block_point.row - cursor.start().0.0; - let wrap_row = cursor.start().1.0 + overshoot; + let overshoot = block_point.row - start.0.0; + let wrap_row = start.1.0 + overshoot; WrapPoint::new(wrap_row, block_point.column) } } diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index e5d82f8f70a9b5e29622b1302c1eaaf2070b0387..d3bc7acfd303d7952cc46001306067dabb5b089f 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -98,28 +98,26 @@ impl FoldPoint { } pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint { - let mut cursor = snapshot + let (start, _, _) = snapshot .transforms - .cursor::>(()); - cursor.seek(&self, Bias::Right); - let overshoot = self.0 - cursor.start().0.0; - InlayPoint(cursor.start().1.0 + overshoot) + .find::, _>((), &self, Bias::Right); + let overshoot = self.0 - start.0.0; + InlayPoint(start.1.0 + overshoot) } pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset { - let mut cursor = snapshot + let (start, _, item) = snapshot .transforms - .cursor::>(()); - cursor.seek(&self, Bias::Right); - let overshoot = self.0 - cursor.start().1.output.lines; - let mut offset = cursor.start().1.output.len; + .find::, _>((), &self, Bias::Right); + let overshoot = self.0 - start.1.output.lines; + let mut offset = start.1.output.len; if !overshoot.is_zero() { - let transform = cursor.item().expect("display point out of range"); + let transform = item.expect("display point out of range"); assert!(transform.placeholder.is_none()); let end_inlay_offset = snapshot .inlay_snapshot - .to_offset(InlayPoint(cursor.start().1.input.lines + overshoot)); - offset += end_inlay_offset.0 - cursor.start().1.input.len; + .to_offset(InlayPoint(start.1.input.lines + overshoot)); + offset += end_inlay_offset.0 - start.1.input.len; } FoldOffset(offset) } @@ -706,19 +704,18 @@ impl FoldSnapshot { } pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint { - let mut cursor = self + let (start, end, item) = self .transforms - .cursor::>(()); - 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 + .find::, _>((), &point, Bias::Right); + if item.is_some_and(|t| t.is_fold()) { + if bias == Bias::Left || point == start.0 { + start.1 } else { - cursor.end().1 + end.1 } } else { - let overshoot = point.0 - cursor.start().0.0; - FoldPoint(cmp::min(cursor.start().1.0 + overshoot, cursor.end().1.0)) + let overshoot = point.0 - start.0.0; + FoldPoint(cmp::min(start.1.0 + overshoot, end.1.0)) } } @@ -787,9 +784,10 @@ impl FoldSnapshot { { let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer); let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset); - let mut cursor = self.transforms.cursor::(()); - cursor.seek(&inlay_offset, Bias::Right); - cursor.item().is_some_and(|t| t.placeholder.is_some()) + let (_, _, item) = self + .transforms + .find::((), &inlay_offset, Bias::Right); + item.is_some_and(|t| t.placeholder.is_some()) } pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool { @@ -891,23 +889,22 @@ impl FoldSnapshot { } pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint { - let mut cursor = self + let (start, end, item) = self .transforms - .cursor::>(()); - cursor.seek(&point, Bias::Right); - if let Some(transform) = cursor.item() { - let transform_start = cursor.start().0.0; + .find::, _>((), &point, Bias::Right); + if let Some(transform) = item { + let transform_start = start.0.0; if transform.placeholder.is_some() { if point.0 == transform_start || matches!(bias, Bias::Left) { FoldPoint(transform_start) } else { - FoldPoint(cursor.end().0.0) + FoldPoint(end.0.0) } } else { let overshoot = InlayPoint(point.0 - transform_start); - let inlay_point = cursor.start().1 + overshoot; + let inlay_point = start.1 + overshoot; let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias); - FoldPoint(cursor.start().0.0 + (clipped_inlay_point - cursor.start().1).0) + FoldPoint(start.0.0 + (clipped_inlay_point - start.1).0) } } else { FoldPoint(self.transforms.summary().output.lines) @@ -1480,28 +1477,26 @@ pub struct FoldOffset(pub usize); impl FoldOffset { pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint { - let mut cursor = snapshot + let (start, _, item) = snapshot .transforms - .cursor::>(()); - cursor.seek(&self, Bias::Right); - let overshoot = if cursor.item().is_none_or(|t| t.is_fold()) { - Point::new(0, (self.0 - cursor.start().0.0) as u32) + .find::, _>((), &self, Bias::Right); + let overshoot = if item.is_none_or(|t| t.is_fold()) { + Point::new(0, (self.0 - start.0.0) as u32) } else { - let inlay_offset = cursor.start().1.input.len + self.0 - cursor.start().0.0; + let inlay_offset = start.1.input.len + self.0 - start.0.0; let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset)); - inlay_point.0 - cursor.start().1.input.lines + inlay_point.0 - start.1.input.lines }; - FoldPoint(cursor.start().1.output.lines + overshoot) + FoldPoint(start.1.output.lines + overshoot) } #[cfg(test)] pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset { - let mut cursor = snapshot + let (start, _, _) = snapshot .transforms - .cursor::>(()); - cursor.seek(&self, Bias::Right); - let overshoot = self.0 - cursor.start().0.0; - InlayOffset(cursor.start().1.0 + overshoot) + .find::, _>((), &self, Bias::Right); + let overshoot = self.0 - start.0.0; + InlayOffset(start.1.0 + overshoot) } } diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 7aeb14fe0eef687ed375e28c6a726799e3876b12..8bda58da173ad45c524c5062bed6daf8309d114d 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -825,22 +825,21 @@ impl InlayMap { impl InlaySnapshot { pub fn to_point(&self, offset: InlayOffset) -> InlayPoint { - let mut cursor = self + let (start, _, item) = self .transforms - .cursor::>(()); - cursor.seek(&offset, Bias::Right); - let overshoot = offset.0 - cursor.start().0.0; - match cursor.item() { + .find::, _>((), &offset, Bias::Right); + let overshoot = offset.0 - start.0.0; + match item { Some(Transform::Isomorphic(_)) => { - let buffer_offset_start = cursor.start().2; + let buffer_offset_start = start.2; let buffer_offset_end = buffer_offset_start + overshoot; let buffer_start = self.buffer.offset_to_point(buffer_offset_start); let buffer_end = self.buffer.offset_to_point(buffer_offset_end); - InlayPoint(cursor.start().1.0 + (buffer_end - buffer_start)) + InlayPoint(start.1.0 + (buffer_end - buffer_start)) } Some(Transform::Inlay(inlay)) => { let overshoot = inlay.text().offset_to_point(overshoot); - InlayPoint(cursor.start().1.0 + overshoot) + InlayPoint(start.1.0 + overshoot) } None => self.max_point(), } @@ -855,47 +854,48 @@ impl InlaySnapshot { } pub fn to_offset(&self, point: InlayPoint) -> InlayOffset { - let mut cursor = self + let (start, _, item) = self .transforms - .cursor::>(()); - cursor.seek(&point, Bias::Right); - let overshoot = point.0 - cursor.start().0.0; - match cursor.item() { + .find::, _>((), &point, Bias::Right); + let overshoot = point.0 - start.0.0; + match item { Some(Transform::Isomorphic(_)) => { - let buffer_point_start = cursor.start().2; + let buffer_point_start = start.2; let buffer_point_end = buffer_point_start + overshoot; let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start); let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end); - InlayOffset(cursor.start().1.0 + (buffer_offset_end - buffer_offset_start)) + InlayOffset(start.1.0 + (buffer_offset_end - buffer_offset_start)) } Some(Transform::Inlay(inlay)) => { let overshoot = inlay.text().point_to_offset(overshoot); - InlayOffset(cursor.start().1.0 + overshoot) + InlayOffset(start.1.0 + overshoot) } None => self.len(), } } pub fn to_buffer_point(&self, point: InlayPoint) -> Point { - let mut cursor = self.transforms.cursor::>(()); - cursor.seek(&point, Bias::Right); - match cursor.item() { + let (start, _, item) = + self.transforms + .find::, _>((), &point, Bias::Right); + match item { Some(Transform::Isomorphic(_)) => { - let overshoot = point.0 - cursor.start().0.0; - cursor.start().1 + overshoot + let overshoot = point.0 - start.0.0; + start.1 + overshoot } - Some(Transform::Inlay(_)) => cursor.start().1, + Some(Transform::Inlay(_)) => start.1, None => self.buffer.max_point(), } } pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize { - let mut cursor = self.transforms.cursor::>(()); - cursor.seek(&offset, Bias::Right); - match cursor.item() { + let (start, _, item) = + self.transforms + .find::, _>((), &offset, Bias::Right); + match item { Some(Transform::Isomorphic(_)) => { - let overshoot = offset - cursor.start().0; - cursor.start().1 + overshoot.0 + let overshoot = offset - start.0; + start.1 + overshoot.0 } - Some(Transform::Inlay(_)) => cursor.start().1, + Some(Transform::Inlay(_)) => start.1, None => self.buffer.len(), } } diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 39c247cb4e105155e77c8fd5c84e5f185d726af1..6c1ee61de09327d06c17ed977c5f236c2d7232e8 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -568,14 +568,17 @@ impl WrapSnapshot { let mut old_start = old_cursor.start().output.lines; old_start += tab_edit.old.start.0 - old_cursor.start().input.lines; + // todo(lw): Should these be seek_forward? old_cursor.seek(&tab_edit.old.end, Bias::Right); let mut old_end = old_cursor.start().output.lines; old_end += tab_edit.old.end.0 - old_cursor.start().input.lines; + // todo(lw): Should these be seek_forward? new_cursor.seek(&tab_edit.new.start, Bias::Right); let mut new_start = new_cursor.start().output.lines; new_start += tab_edit.new.start.0 - new_cursor.start().input.lines; + // todo(lw): Should these be seek_forward? new_cursor.seek(&tab_edit.new.end, Bias::Right); let mut new_end = new_cursor.start().output.lines; new_end += tab_edit.new.end.0 - new_cursor.start().input.lines; @@ -628,24 +631,22 @@ impl WrapSnapshot { } pub fn line_len(&self, row: u32) -> u32 { - let mut cursor = self - .transforms - .cursor::>(()); - cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left); - if cursor - .item() - .is_some_and(|transform| transform.is_isomorphic()) - { - let overshoot = row - cursor.start().0.row(); - let tab_row = cursor.start().1.row() + overshoot; + let (start, _, item) = self.transforms.find::, _>( + (), + &WrapPoint::new(row + 1, 0), + Bias::Left, + ); + if item.is_some_and(|transform| transform.is_isomorphic()) { + let overshoot = row - start.0.row(); + let tab_row = 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()) + start.0.column() + (tab_line_len - start.1.column()) } else { tab_line_len } } else { - cursor.start().0.column() + start.0.column() } } @@ -711,9 +712,10 @@ impl WrapSnapshot { } pub fn soft_wrap_indent(&self, row: u32) -> Option { - let mut cursor = self.transforms.cursor::(()); - cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right); - cursor.item().and_then(|transform| { + let (.., item) = + self.transforms + .find::((), &WrapPoint::new(row + 1, 0), Bias::Right); + item.and_then(|transform| { if transform.is_isomorphic() { None } else { @@ -749,13 +751,12 @@ impl WrapSnapshot { } pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint { - let mut cursor = self - .transforms - .cursor::>(()); - cursor.seek(&point, Bias::Right); - let mut tab_point = cursor.start().1.0; - if cursor.item().is_some_and(|t| t.is_isomorphic()) { - tab_point += point.0 - cursor.start().0.0; + let (start, _, item) = + self.transforms + .find::, _>((), &point, Bias::Right); + let mut tab_point = start.1.0; + if item.is_some_and(|t| t.is_isomorphic()) { + tab_point += point.0 - start.0.0; } TabPoint(tab_point) } @@ -769,19 +770,19 @@ impl WrapSnapshot { } pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint { - let mut cursor = self - .transforms - .cursor::>(()); - cursor.seek(&point, Bias::Right); - WrapPoint(cursor.start().1.0 + (point.0 - cursor.start().0.0)) + let (start, ..) = + self.transforms + .find::, _>((), &point, Bias::Right); + WrapPoint(start.1.0 + (point.0 - start.0.0)) } pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint { if bias == Bias::Left { - let mut cursor = self.transforms.cursor::(()); - cursor.seek(&point, Bias::Right); - if cursor.item().is_some_and(|t| !t.is_isomorphic()) { - point = *cursor.start(); + let (start, _, item) = self + .transforms + .find::((), &point, Bias::Right); + if item.is_some_and(|t| !t.is_isomorphic()) { + point = start; *point.column_mut() -= 1; } } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 0d2b9971d8074a3051b31f44faf91f9c734f3064..78566208c89a7d6bf73804f611b45aa70e4933ec 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -509,10 +509,11 @@ impl StateInner { if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max { self.logical_scroll_top = None; } else { - let mut cursor = self.items.cursor::(()); - cursor.seek(&Height(new_scroll_top), Bias::Right); - let item_ix = cursor.start().count; - let offset_in_item = new_scroll_top - cursor.start().height; + let (start, ..) = + self.items + .find::((), &Height(new_scroll_top), Bias::Right); + let item_ix = start.count; + let offset_in_item = new_scroll_top - start.height; self.logical_scroll_top = Some(ListOffset { item_ix, offset_in_item, @@ -550,9 +551,12 @@ impl StateInner { } fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels { - let mut cursor = self.items.cursor::(()); - cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right); - cursor.start().height + logical_scroll_top.offset_in_item + let (start, ..) = self.items.find::( + (), + &Count(logical_scroll_top.item_ix), + Bias::Right, + ); + start.height + logical_scroll_top.offset_in_item } fn layout_all_items( @@ -882,11 +886,12 @@ impl StateInner { if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max { self.logical_scroll_top = None; } else { - let mut cursor = self.items.cursor::(()); - cursor.seek(&Height(new_scroll_top), Bias::Right); + let (start, _, _) = + self.items + .find::((), &Height(new_scroll_top), Bias::Right); - let item_ix = cursor.start().count; - let offset_in_item = new_scroll_top - cursor.start().height; + let item_ix = start.count; + let offset_in_item = new_scroll_top - start.height; self.logical_scroll_top = Some(ListOffset { item_ix, offset_in_item, diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 94966708ac0e49fc01c7e1617fdd41149fc0a4e9..db15a0774c5411b1ddfb8a3f9772a0ca9789c36c 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -6140,9 +6140,8 @@ impl MultiBufferSnapshot { } else if id == ExcerptId::max() { Locator::max_ref() } else { - let mut cursor = self.excerpt_ids.cursor::(()); - cursor.seek(&id, Bias::Left); - if let Some(entry) = cursor.item() + let (_, _, item) = self.excerpt_ids.find::((), &id, Bias::Left); + if let Some(entry) = item && entry.id == id { return &entry.locator; diff --git a/crates/notifications/src/notification_store.rs b/crates/notifications/src/notification_store.rs index 0964a648b0bead5d46fe7d63113d6bc966673116..7cae74a7293694ebedd603ded656af00201c7366 100644 --- a/crates/notifications/src/notification_store.rs +++ b/crates/notifications/src/notification_store.rs @@ -123,14 +123,16 @@ impl NotificationStore { return None; } let ix = count - 1 - ix; - let mut cursor = self.notifications.cursor::(()); - cursor.seek(&Count(ix), Bias::Right); - cursor.item() + let (.., item) = self + .notifications + .find::((), &Count(ix), Bias::Right); + item } pub fn notification_for_id(&self, id: u64) -> Option<&NotificationEntry> { - let mut cursor = self.notifications.cursor::(()); - cursor.seek(&NotificationId(id), Bias::Left); - if let Some(item) = cursor.item() + let (.., item) = + self.notifications + .find::((), &NotificationId(id), Bias::Left); + if let Some(item) = item && item.id == id { return Some(item); diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index b0b34c95120acddb26081b9265346c80a5afc99c..44e0676bae79bf3e3dee92e4a16652f404c95273 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -41,12 +41,9 @@ impl Rope { if self.chunks.is_empty() { return offset == 0; } - let mut cursor = self.chunks.cursor::(()); - cursor.seek(&offset, Bias::Left); - let chunk_offset = offset - cursor.start(); - cursor - .item() - .map(|chunk| chunk.text.is_char_boundary(chunk_offset)) + let (start, _, item) = self.chunks.find::((), &offset, Bias::Left); + let chunk_offset = offset - start; + item.map(|chunk| chunk.text.is_char_boundary(chunk_offset)) .unwrap_or(false) } @@ -60,10 +57,9 @@ impl Rope { (u8 as i8) >= -0x40 } - let mut cursor = self.chunks.cursor::(()); - cursor.seek(&index, Bias::Left); - let chunk_offset = index - cursor.start(); - let lower_idx = cursor.item().map(|chunk| { + let (start, _, item) = self.chunks.find::((), &index, Bias::Left); + let chunk_offset = index - start; + let lower_idx = item.map(|chunk| { let lower_bound = chunk_offset.saturating_sub(3); chunk .text @@ -78,7 +74,7 @@ impl Rope { }) .unwrap_or(chunk.text.len()) }); - lower_idx.map_or_else(|| self.len(), |idx| cursor.start() + idx) + lower_idx.map_or_else(|| self.len(), |idx| start + idx) } } @@ -92,10 +88,9 @@ impl Rope { (u8 as i8) >= -0x40 } - let mut cursor = self.chunks.cursor::(()); - cursor.seek(&index, Bias::Left); - let chunk_offset = index - cursor.start(); - let upper_idx = cursor.item().map(|chunk| { + let (start, _, item) = self.chunks.find::((), &index, Bias::Left); + let chunk_offset = index - start; + let upper_idx = item.map(|chunk| { let upper_bound = Ord::min(chunk_offset + 4, chunk.text.len()); chunk.text.as_bytes()[chunk_offset..upper_bound] .iter() @@ -103,7 +98,7 @@ impl Rope { .map_or(upper_bound, |pos| pos + chunk_offset) }); - upper_idx.map_or_else(|| self.len(), |idx| cursor.start() + idx) + upper_idx.map_or_else(|| self.len(), |idx| start + idx) } } @@ -356,11 +351,12 @@ impl Rope { if offset >= self.summary().len { return self.summary().len_utf16; } - let mut cursor = self.chunks.cursor::>(()); - cursor.seek(&offset, Bias::Left); - let overshoot = offset - cursor.start().0; - cursor.start().1 - + cursor.item().map_or(Default::default(), |chunk| { + let (start, _, item) = + self.chunks + .find::, _>((), &offset, Bias::Left); + let overshoot = offset - start.0; + start.1 + + item.map_or(Default::default(), |chunk| { chunk.as_slice().offset_to_offset_utf16(overshoot) }) } diff --git a/crates/sum_tree/src/cursor.rs b/crates/sum_tree/src/cursor.rs index 6df1d3da41556dc7eb93f7460b960ccddbe52de6..7418224c86f51a52a8a621da0f2a0c53dcfcf404 100644 --- a/crates/sum_tree/src/cursor.rs +++ b/crates/sum_tree/src/cursor.rs @@ -388,6 +388,7 @@ where T: Item, D: Dimension<'a, T::Summary>, { + /// Returns whether we found the item you were seeking for. #[track_caller] pub fn seek(&mut self, pos: &Target, bias: Bias) -> bool where @@ -397,6 +398,7 @@ where self.seek_internal(pos, bias, &mut ()) } + /// Returns whether we found the item you were seeking for. #[track_caller] pub fn seek_forward(&mut self, pos: &Target, bias: Bias) -> bool where @@ -437,7 +439,7 @@ where summary.0 } - /// Returns whether we found the item you were seeking for + /// Returns whether we found the item you were seeking for. #[track_caller] fn seek_internal( &mut self, diff --git a/crates/sum_tree/src/sum_tree.rs b/crates/sum_tree/src/sum_tree.rs index bfd2423c9230ea4246509e164d7a03f4890cbf4a..ab0e9d03c4594b89159893c7a671e8a9e3928b3f 100644 --- a/crates/sum_tree/src/sum_tree.rs +++ b/crates/sum_tree/src/sum_tree.rs @@ -82,6 +82,11 @@ pub trait Dimension<'a, S: Summary>: Clone { fn zero(cx: S::Context<'_>) -> Self; fn add_summary(&mut self, summary: &'a S, cx: S::Context<'_>); + #[must_use] + fn with_added_summary(mut self, summary: &'a S, cx: S::Context<'_>) -> Self { + self.add_summary(summary, cx); + self + } fn from_summary(summary: &'a S, cx: S::Context<'_>) -> Self { let mut dimension = Self::zero(cx); @@ -371,12 +376,122 @@ impl SumTree { Iter::new(self) } - pub fn cursor<'a, 'b, S>( + /// A more efficient version of `Cursor::new()` + `Cursor::seek()` + `Cursor::item()`. + /// + /// Only returns the item that exactly has the target match. + pub fn find_exact<'a, 'slf, D, Target>( + &'slf self, + cx: ::Context<'a>, + target: &Target, + bias: Bias, + ) -> (D, D, Option<&'slf T>) + where + D: Dimension<'slf, T::Summary>, + Target: SeekTarget<'slf, T::Summary, D>, + { + let tree_end = D::zero(cx).with_added_summary(self.summary(), cx); + let comparison = target.cmp(&tree_end, cx); + if comparison == Ordering::Greater || (comparison == Ordering::Equal && bias == Bias::Right) + { + return (tree_end.clone(), tree_end, None); + } + + let mut pos = D::zero(cx); + return match Self::find_recurse::<_, _, true>(cx, target, bias, &mut pos, self) { + Some((item, end)) => (pos, end, Some(item)), + None => (pos.clone(), pos, None), + }; + } + + /// A more efficient version of `Cursor::new()` + `Cursor::seek()` + `Cursor::item()` + pub fn find<'a, 'slf, D, Target>( + &'slf self, + cx: ::Context<'a>, + target: &Target, + bias: Bias, + ) -> (D, D, Option<&'slf T>) + where + D: Dimension<'slf, T::Summary>, + Target: SeekTarget<'slf, T::Summary, D>, + { + let tree_end = D::zero(cx).with_added_summary(self.summary(), cx); + let comparison = target.cmp(&tree_end, cx); + if comparison == Ordering::Greater || (comparison == Ordering::Equal && bias == Bias::Right) + { + return (tree_end.clone(), tree_end, None); + } + + let mut pos = D::zero(cx); + return match Self::find_recurse::<_, _, false>(cx, target, bias, &mut pos, self) { + Some((item, end)) => (pos, end, Some(item)), + None => (pos.clone(), pos, None), + }; + } + + fn find_recurse<'tree, 'a, D, Target, const EXACT: bool>( + cx: ::Context<'a>, + target: &Target, + bias: Bias, + position: &mut D, + this: &'tree SumTree, + ) -> Option<(&'tree T, D)> + where + D: Dimension<'tree, T::Summary>, + Target: SeekTarget<'tree, T::Summary, D>, + { + match &*this.0 { + Node::Internal { + child_summaries, + child_trees, + .. + } => { + for (child_tree, child_summary) in child_trees.iter().zip(child_summaries) { + let child_end = position.clone().with_added_summary(child_summary, cx); + + let comparison = target.cmp(&child_end, cx); + let target_in_child = comparison == Ordering::Less + || (comparison == Ordering::Equal && bias == Bias::Left); + if target_in_child { + return Self::find_recurse::( + cx, target, bias, position, child_tree, + ); + } + *position = child_end; + } + } + Node::Leaf { + items, + item_summaries, + .. + } => { + for (item, item_summary) in items.iter().zip(item_summaries) { + let mut child_end = position.clone(); + child_end.add_summary(item_summary, cx); + + let comparison = target.cmp(&child_end, cx); + let entry_found = if EXACT { + comparison == Ordering::Equal + } else { + comparison == Ordering::Less + || (comparison == Ordering::Equal && bias == Bias::Left) + }; + if entry_found { + return Some((item, child_end)); + } + + *position = child_end; + } + } + } + None + } + + pub fn cursor<'a, 'b, D>( &'a self, cx: ::Context<'b>, - ) -> Cursor<'a, 'b, T, S> + ) -> Cursor<'a, 'b, T, D> where - S: Dimension<'a, T::Summary>, + D: Dimension<'a, T::Summary>, { Cursor::new(self, cx) } @@ -787,9 +902,8 @@ impl SumTree { key: &T::Key, cx: ::Context<'a>, ) -> Option<&'a T> { - let mut cursor = self.cursor::(cx); - if cursor.seek(key, Bias::Left) { - cursor.item() + if let (_, _, Some(item)) = self.find_exact::(cx, key, Bias::Left) { + Some(item) } else { None } diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index 62630407083fc34d74fef0ebf5e17a44ce40f68e..3e56194dddd9910f819e91c209f6701b55efdd02 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -54,9 +54,10 @@ impl TreeMap { } pub fn get(&self, key: &K) -> Option<&V> { - let mut cursor = self.0.cursor::>(()); - cursor.seek(&MapKeyRef(Some(key)), Bias::Left); - if let Some(item) = cursor.item() { + let (.., item) = self + .0 + .find::, _>((), &MapKeyRef(Some(key)), Bias::Left); + if let Some(item) = item { if Some(key) == item.key().0.as_ref() { Some(&item.value) } else { diff --git a/crates/text/src/anchor.rs b/crates/text/src/anchor.rs index 6b0db2f9352997cd5cef4544edc388bc9c5cd209..cf2febdfc505b426fd8d224a2dc29f18d22cd1a8 100644 --- a/crates/text/src/anchor.rs +++ b/crates/text/src/anchor.rs @@ -99,13 +99,14 @@ impl Anchor { let Some(fragment_id) = buffer.try_fragment_id_for_anchor(self) else { return false; }; - let mut fragment_cursor = buffer + let (.., item) = buffer .fragments - .cursor::, usize>>(&None); - fragment_cursor.seek(&Some(fragment_id), Bias::Left); - fragment_cursor - .item() - .is_some_and(|fragment| fragment.visible) + .find::, usize>, _>( + &None, + &Some(fragment_id), + Bias::Left, + ); + item.is_some_and(|fragment| fragment.visible) } } } diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 0516bf21c949db266aad025500f51aab9cec0958..42d9a48430590302f96a1476bdbc3b876ed8f2f6 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -2323,12 +2323,15 @@ impl BufferSnapshot { ); }; - let mut fragment_cursor = self + let (start, _, item) = self .fragments - .cursor::, usize>>(&None); - fragment_cursor.seek(&Some(&insertion.fragment_id), Bias::Left); - let fragment = fragment_cursor.item().unwrap(); - let mut fragment_offset = fragment_cursor.start().1; + .find::, usize>, _>( + &None, + &Some(&insertion.fragment_id), + Bias::Left, + ); + let fragment = item.unwrap(); + let mut fragment_offset = start.1; if fragment.visible { fragment_offset += anchor.offset - insertion.split_offset; } @@ -2410,10 +2413,9 @@ impl BufferSnapshot { offset, ch, char_range, ); } - let mut fragment_cursor = self.fragments.cursor::(&None); - fragment_cursor.seek(&offset, bias); - let fragment = fragment_cursor.item().unwrap(); - let overshoot = offset - *fragment_cursor.start(); + let (start, _, item) = self.fragments.find::(&None, &offset, bias); + let fragment = item.unwrap(); + let overshoot = offset - start; Anchor { timestamp: fragment.timestamp, offset: fragment.insertion_offset + overshoot, @@ -2494,15 +2496,17 @@ impl BufferSnapshot { cursor.next(); Some(cursor) }; - let mut cursor = self - .fragments - .cursor::, FragmentTextSummary>>(&None); - let start_fragment_id = self.fragment_id_for_anchor(&range.start); - cursor.seek(&Some(start_fragment_id), Bias::Left); - let mut visible_start = cursor.start().1.visible; - let mut deleted_start = cursor.start().1.deleted; - if let Some(fragment) = cursor.item() { + let (start, _, item) = self + .fragments + .find::, FragmentTextSummary>, _>( + &None, + &Some(start_fragment_id), + Bias::Left, + ); + let mut visible_start = start.1.visible; + let mut deleted_start = start.1.deleted; + if let Some(fragment) = item { let overshoot = range.start.offset - fragment.insertion_offset; if fragment.visible { visible_start += overshoot;