Improve range-based selection queries to only resolve the requested selections

Nathan Sobo created

Change summary

crates/buffer/src/anchor.rs    | 20 ++++++++++++----
crates/buffer/src/selection.rs | 23 +++++++++++++++++++
crates/editor/src/element.rs   | 12 +++++----
crates/editor/src/lib.rs       | 43 +++++++++++------------------------
4 files changed, 59 insertions(+), 39 deletions(-)

Detailed changes

crates/buffer/src/anchor.rs 🔗

@@ -210,14 +210,14 @@ impl<T> AnchorRangeMap<T> {
             .zip(self.entries.iter().map(|e| &e.1))
     }
 
-    pub fn intersecting_ranges<'a, D, T>(
+    pub fn intersecting_ranges<'a, D, I>(
         &'a self,
-        range: Range<(T, Bias)>,
+        range: Range<(I, Bias)>,
         content: impl Into<Content<'a>> + 'a,
     ) -> impl Iterator<Item = (Range<D>, &'a T)> + 'a
     where
         D: 'a + TextDimension<'a>,
-        T: ToOffset,
+        I: ToOffset,
     {
         let content = content.into();
         let range = content.anchor_at(range.start.0, range.start.1)
@@ -229,10 +229,20 @@ impl<T> AnchorRangeMap<T> {
             version: self.version.clone(),
         };
         let start_ix = self.entries.binary_search_by(|probe| {
-            probe_anchor.full_offset = probe.0.start;
+            probe_anchor.full_offset = probe.0.end;
             probe_anchor.cmp(&range.start, &content).unwrap()
         });
-        std::iter::empty()
+
+        match start_ix {
+            Ok(start_ix) | Err(start_ix) => content
+                .summaries_for_anchor_ranges(
+                    self.version.clone(),
+                    self.start_bias,
+                    self.end_bias,
+                    self.entries[start_ix..].iter().map(|e| &e.0),
+                )
+                .zip(self.entries.iter().map(|e| &e.1)),
+        }
     }
 
     pub fn full_offset_ranges(&self) -> impl Iterator<Item = &(Range<FullOffset>, T)> {

crates/buffer/src/selection.rs 🔗

@@ -1,3 +1,5 @@
+use sum_tree::Bias;
+
 use crate::rope::TextDimension;
 
 use super::{AnchorRangeMap, Buffer, Content, Point, ToOffset, ToPoint};
@@ -117,6 +119,27 @@ impl SelectionSet {
             })
     }
 
+    pub fn intersecting_selections<'a, D, I, C>(
+        &'a self,
+        range: Range<(I, Bias)>,
+        content: C,
+    ) -> impl 'a + Iterator<Item = Selection<D>>
+    where
+        D: 'a + TextDimension<'a>,
+        I: 'a + ToOffset,
+        C: 'a + Into<Content<'a>>,
+    {
+        self.selections
+            .intersecting_ranges(range, content)
+            .map(|(range, state)| Selection {
+                id: state.id,
+                start: range.start,
+                end: range.end,
+                reversed: state.reversed,
+                goal: state.goal,
+            })
+    }
+
     pub fn oldest_selection<'a, D, C>(&'a self, content: C) -> Option<Selection<D>>
     where
         D: 'a + TextDimension<'a>,

crates/editor/src/element.rs 🔗

@@ -748,11 +748,13 @@ impl Element for EditorElement {
         self.update_view(cx.app, |view, cx| {
             highlighted_row = view.highlighted_row();
             for selection_set_id in view.active_selection_sets(cx).collect::<Vec<_>>() {
-                let replica_selections = view.selections_in_range(
-                    selection_set_id,
-                    DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
-                    cx,
-                );
+                let replica_selections = view
+                    .intersecting_selections(
+                        selection_set_id,
+                        DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
+                        cx,
+                    )
+                    .collect::<Vec<_>>();
                 for selection in &replica_selections {
                     if selection_set_id == view.selection_set_id {
                         let is_empty = selection.start == selection.end;

crates/editor/src/lib.rs 🔗

@@ -3017,23 +3017,15 @@ impl Editor {
             .map(|(set_id, _)| *set_id)
     }
 
-    pub fn selections_in_range<'a>(
+    pub fn intersecting_selections<'a>(
         &'a self,
         set_id: SelectionSetId,
         range: Range<DisplayPoint>,
         cx: &'a mut MutableAppContext,
-    ) -> Vec<Selection<DisplayPoint>> {
+    ) -> impl 'a + Iterator<Item = Selection<DisplayPoint>> {
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = self.buffer.read(cx);
-        let selections = self
-            .buffer
-            .read(cx)
-            .selection_set(set_id)
-            .unwrap()
-            .selections::<Point, _>(buffer)
-            .collect::<Vec<_>>();
-        let start = range.start.to_point(&display_map);
-        let start_index = self.selection_insertion_index(&selections, start);
+
         let pending_selection = if set_id == self.selection_set_id {
             self.pending_selection.as_ref().and_then(|pending| {
                 let selection_start = pending.selection.start.to_display_point(&display_map);
@@ -3053,9 +3045,17 @@ impl Editor {
         } else {
             None
         };
+
+        let range = (range.start.to_offset(&display_map, Bias::Left), Bias::Left)
+            ..(range.end.to_offset(&display_map, Bias::Left), Bias::Right);
+        let selections = self
+            .buffer
+            .read(cx)
+            .selection_set(set_id)
+            .unwrap()
+            .intersecting_selections::<Point, _, _>(range, buffer);
+
         selections
-            .into_iter()
-            .skip(start_index)
             .map(move |s| Selection {
                 id: s.id,
                 start: s.start.to_display_point(&display_map),
@@ -3063,22 +3063,7 @@ impl Editor {
                 reversed: s.reversed,
                 goal: s.goal,
             })
-            .take_while(move |r| r.start <= range.end || r.end <= range.end)
             .chain(pending_selection)
-            .collect()
-    }
-
-    fn selection_insertion_index(&self, selections: &[Selection<Point>], start: Point) -> usize {
-        match selections.binary_search_by_key(&start, |probe| probe.start) {
-            Ok(index) => index,
-            Err(index) => {
-                if index > 0 && selections[index - 1].end > start {
-                    index - 1
-                } else {
-                    index
-                }
-            }
-        }
     }
 
     pub fn selections<'a, D>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Selection<D>>
@@ -5694,7 +5679,7 @@ mod tests {
 
     impl Editor {
         fn selection_ranges(&self, cx: &mut MutableAppContext) -> Vec<Range<DisplayPoint>> {
-            self.selections_in_range(
+            self.intersecting_selections(
                 self.selection_set_id,
                 DisplayPoint::zero()..self.max_point(cx),
                 cx,