Finish removing anchor collections from MultiBuffer

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/language/src/diagnostic_set.rs         |  12 
crates/language/src/multi_buffer.rs           | 140 +++++++----
crates/language/src/multi_buffer/anchor.rs    | 244 --------------------
crates/language/src/multi_buffer/location.rs  |  76 ------
crates/language/src/multi_buffer/selection.rs | 122 ++++++---
crates/text/src/text.rs                       |   2 
6 files changed, 181 insertions(+), 415 deletions(-)

Detailed changes

crates/language/src/diagnostic_set.rs 🔗

@@ -28,7 +28,7 @@ pub struct Summary {
 }
 
 impl DiagnosticSet {
-    pub fn from_sorted_entries<I>(iter: I, buffer: &text::Snapshot) -> Self
+    pub fn from_sorted_entries<I>(iter: I, buffer: &text::BufferSnapshot) -> Self
     where
         I: IntoIterator<Item = DiagnosticEntry<Anchor>>,
     {
@@ -37,7 +37,7 @@ impl DiagnosticSet {
         }
     }
 
-    pub fn new<I>(iter: I, buffer: &text::Snapshot) -> Self
+    pub fn new<I>(iter: I, buffer: &text::BufferSnapshot) -> Self
     where
         I: IntoIterator<Item = DiagnosticEntry<PointUtf16>>,
     {
@@ -62,7 +62,7 @@ impl DiagnosticSet {
     pub fn range<'a, T, O>(
         &'a self,
         range: Range<T>,
-        buffer: &'a text::Snapshot,
+        buffer: &'a text::BufferSnapshot,
         inclusive: bool,
     ) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
     where
@@ -101,7 +101,7 @@ impl DiagnosticSet {
     pub fn group<'a, O: FromAnchor>(
         &'a self,
         group_id: usize,
-        buffer: &'a text::Snapshot,
+        buffer: &'a text::BufferSnapshot,
     ) -> impl 'a + Iterator<Item = DiagnosticEntry<O>> {
         self.iter()
             .filter(move |entry| entry.diagnostic.group_id == group_id)
@@ -124,7 +124,7 @@ impl sum_tree::Item for DiagnosticEntry<Anchor> {
 }
 
 impl DiagnosticEntry<Anchor> {
-    pub fn resolve<O: FromAnchor>(&self, buffer: &text::Snapshot) -> DiagnosticEntry<O> {
+    pub fn resolve<O: FromAnchor>(&self, buffer: &text::BufferSnapshot) -> DiagnosticEntry<O> {
         DiagnosticEntry {
             range: O::from_anchor(&self.range.start, buffer)
                 ..O::from_anchor(&self.range.end, buffer),
@@ -146,7 +146,7 @@ impl Default for Summary {
 }
 
 impl sum_tree::Summary for Summary {
-    type Context = text::Snapshot;
+    type Context = text::BufferSnapshot;
 
     fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
         if other

crates/language/src/multi_buffer.rs 🔗

@@ -1,32 +1,36 @@
 mod anchor;
-mod location;
 mod selection;
 
-use self::location::*;
 use crate::{
     buffer::{self, Buffer, Chunk, ToOffset as _, ToPoint as _},
     BufferSnapshot, Diagnostic, File, Language,
 };
+pub use anchor::{Anchor, AnchorRangeExt};
 use anyhow::Result;
 use clock::ReplicaId;
 use collections::HashMap;
 use gpui::{AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task};
 use parking_lot::{Mutex, MutexGuard};
-use smallvec::SmallVec;
-use std::{cmp, io, ops::Range, sync::Arc, time::SystemTime};
+pub use selection::SelectionSet;
+use std::{
+    cmp, io,
+    ops::{Range, Sub},
+    sync::Arc,
+    time::SystemTime,
+};
 use sum_tree::{Bias, Cursor, SumTree};
 use text::{
+    locator::Locator,
     rope::TextDimension,
     subscription::{Subscription, Topic},
     AnchorRangeExt as _, Edit, Point, PointUtf16, Selection, SelectionSetId, TextSummary,
 };
 use theme::SyntaxTheme;
 
-pub use anchor::{Anchor, AnchorRangeExt, AnchorRangeMap, AnchorRangeSet};
-pub use selection::SelectionSet;
-
 const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
 
+pub type ExcerptId = Locator;
+
 #[derive(Default)]
 pub struct MultiBuffer {
     snapshot: Mutex<MultiBufferSnapshot>,
@@ -314,10 +318,10 @@ impl MultiBuffer {
 
         let mut edits = Vec::new();
         let mut new_excerpts = SumTree::new();
-        let mut cursor = snapshot.excerpts.cursor::<(ExcerptId, usize)>();
+        let mut cursor = snapshot.excerpts.cursor::<(Option<&ExcerptId>, usize)>();
 
         for (id, buffer_state) in excerpts_to_edit {
-            new_excerpts.push_tree(cursor.slice(id, Bias::Left, &()), &());
+            new_excerpts.push_tree(cursor.slice(&Some(id), Bias::Left, &()), &());
             let old_excerpt = cursor.item().unwrap();
             let buffer = buffer_state.buffer.read(cx);
 
@@ -411,36 +415,6 @@ impl MultiBuffer {
         self.snapshot.lock().anchor_at(position, bias)
     }
 
-    pub fn anchor_range_map<T, E>(
-        &self,
-        start_bias: Bias,
-        end_bias: Bias,
-        entries: E,
-    ) -> AnchorRangeMap<T>
-    where
-        E: IntoIterator<Item = (Range<usize>, T)>,
-    {
-        let entries = entries.into_iter().peekable();
-        let mut child_maps = SmallVec::new();
-        if let Some((range, _)) = entries.peek() {
-            let mut cursor = self.snapshot.lock().excerpts.cursor::<ExcerptSummary>();
-            cursor.seek(&range.start, Bias::Right, &());
-            let mut excerpt_end = cursor.end(&());
-
-            // child_maps.push
-
-            // for entry in entries {}
-        }
-        AnchorRangeMap { child_maps }
-    }
-
-    pub fn anchor_range_set<E>(&self, start_bias: Bias, end_bias: Bias, ranges: E) -> AnchorRangeSet
-    where
-        E: IntoIterator<Item = Range<usize>>,
-    {
-        todo!()
-    }
-
     pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
         self.snapshot.lock().clip_offset(offset, bias)
     }
@@ -863,6 +837,77 @@ impl MultiBufferSnapshot {
         summary
     }
 
+    fn summary_for_anchor<D>(&self, anchor: &Anchor) -> D
+    where
+        D: TextDimension + Ord + Sub<D, Output = D>,
+    {
+        let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
+        cursor.seek(&Some(&anchor.excerpt_id), Bias::Left, &());
+        if let Some(excerpt) = cursor.item() {
+            if excerpt.id == anchor.excerpt_id {
+                let mut excerpt_start = D::from_text_summary(&cursor.start().text);
+                excerpt_start.add_summary(&excerpt.header_summary(), &());
+                let excerpt_buffer_start = excerpt.range.start.summary::<D>(&excerpt.buffer);
+                let buffer_point = anchor.text_anchor.summary::<D>(&excerpt.buffer);
+                if buffer_point > excerpt_buffer_start {
+                    excerpt_start.add_assign(&(buffer_point - excerpt_buffer_start));
+                }
+                return excerpt_start;
+            }
+        }
+        D::from_text_summary(&cursor.start().text)
+    }
+
+    fn summaries_for_anchors<'a, D, I>(&'a self, anchors: I) -> Vec<D>
+    where
+        D: TextDimension + Ord + Sub<D, Output = D>,
+        I: 'a + IntoIterator<Item = &'a Anchor>,
+    {
+        let mut anchors = anchors.into_iter().peekable();
+        let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
+        let mut summaries = Vec::new();
+        while let Some(anchor) = anchors.peek() {
+            let excerpt_id = &anchor.excerpt_id;
+            cursor.seek(&Some(excerpt_id), Bias::Left, &());
+            if let Some(excerpt) = cursor.item() {
+                let excerpt_exists = excerpt.id == *excerpt_id;
+                let excerpt_anchors = std::iter::from_fn(|| {
+                    let anchor = anchors.peek()?;
+                    if anchor.excerpt_id == *excerpt_id {
+                        Some(&anchors.next().unwrap().text_anchor)
+                    } else {
+                        None
+                    }
+                });
+
+                if excerpt_exists {
+                    let mut excerpt_start = D::from_text_summary(&cursor.start().text);
+                    excerpt_start.add_summary(&excerpt.header_summary(), &());
+                    let excerpt_buffer_start = excerpt.range.start.summary::<D>(&excerpt.buffer);
+                    summaries.extend(
+                        excerpt
+                            .buffer
+                            .summaries_for_anchors::<D, _>(excerpt_anchors)
+                            .map(move |summary| {
+                                let mut excerpt_start = excerpt_start.clone();
+                                let excerpt_buffer_start = excerpt_buffer_start.clone();
+                                if summary > excerpt_buffer_start {
+                                    excerpt_start.add_assign(&(summary - excerpt_buffer_start));
+                                }
+                                excerpt_start
+                            }),
+                    );
+                } else {
+                    excerpt_anchors.for_each(drop);
+                }
+            } else {
+                break;
+            }
+        }
+
+        summaries
+    }
+
     pub fn anchor_before<T: ToOffset>(&self, position: T) -> Anchor {
         self.anchor_at(position, Bias::Left)
     }
@@ -923,12 +968,12 @@ impl MultiBufferSnapshot {
 
     fn buffer_snapshot_for_excerpt<'a>(
         &'a self,
-        excerpt_id: &ExcerptId,
+        excerpt_id: &'a ExcerptId,
     ) -> Option<&'a BufferSnapshot> {
-        let mut cursor = self.excerpts.cursor::<ExcerptId>();
-        cursor.seek(excerpt_id, Bias::Left, &());
+        let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>();
+        cursor.seek(&Some(excerpt_id), Bias::Left, &());
         if let Some(excerpt) = cursor.item() {
-            if cursor.start() == excerpt_id {
+            if *cursor.start() == Some(excerpt_id) {
                 return Some(&excerpt.buffer);
             }
         }
@@ -1020,9 +1065,9 @@ impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize {
     }
 }
 
-impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Location {
+impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Option<&'a ExcerptId> {
     fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
-        Ord::cmp(self, &cursor_location.excerpt_id)
+        Ord::cmp(self, &Some(&cursor_location.excerpt_id))
     }
 }
 
@@ -1038,10 +1083,9 @@ impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 {
     }
 }
 
-impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Location {
+impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a ExcerptId> {
     fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
-        debug_assert!(summary.excerpt_id > *self);
-        *self = summary.excerpt_id.clone();
+        *self = Some(&summary.excerpt_id);
     }
 }
 

crates/language/src/multi_buffer/anchor.rs 🔗

@@ -1,27 +1,18 @@
-use super::{location::*, ExcerptSummary, MultiBufferSnapshot, ToOffset, ToPoint};
+use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToPoint};
 use anyhow::{anyhow, Result};
-use smallvec::SmallVec;
 use std::{
     cmp::Ordering,
     ops::{Range, Sub},
 };
 use sum_tree::Bias;
-use text::{rope::TextDimension, AnchorRangeExt as _, Point};
+use text::{rope::TextDimension, Point};
 
 #[derive(Clone, Eq, PartialEq, Debug, Hash)]
 pub struct Anchor {
-    excerpt_id: ExcerptId,
-    text_anchor: text::Anchor,
+    pub(crate) excerpt_id: ExcerptId,
+    pub(crate) text_anchor: text::Anchor,
 }
 
-#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub struct AnchorRangeMap<T> {
-    pub(crate) child_maps: SmallVec<[(ExcerptId, text::AnchorRangeMap<T>); 1]>,
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct AnchorRangeSet(AnchorRangeMap<()>);
-
 impl Anchor {
     pub fn min() -> Self {
         Self {
@@ -75,234 +66,11 @@ impl Anchor {
         self.clone()
     }
 
-    pub fn summary<'a, D>(&self, snapshot: &'a MultiBufferSnapshot) -> D
+    pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
     where
         D: TextDimension + Ord + Sub<D, Output = D>,
     {
-        let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>();
-        cursor.seek(&self.excerpt_id, Bias::Left, &());
-        if let Some(excerpt) = cursor.item() {
-            if excerpt.id == self.excerpt_id {
-                let mut excerpt_start = D::from_text_summary(&cursor.start().text);
-                excerpt_start.add_summary(&excerpt.header_summary(), &());
-                let excerpt_buffer_start = excerpt.range.start.summary::<D>(&excerpt.buffer);
-                let buffer_point = self.text_anchor.summary::<D>(&excerpt.buffer);
-                if buffer_point > excerpt_buffer_start {
-                    excerpt_start.add_assign(&(buffer_point - excerpt_buffer_start));
-                }
-                return excerpt_start;
-            }
-        }
-        D::from_text_summary(&cursor.start().text)
-    }
-}
-
-impl<T> AnchorRangeMap<T> {
-    pub fn len(&self) -> usize {
-        self.child_maps
-            .iter()
-            .map(|(_, text_map)| text_map.len())
-            .sum()
-    }
-
-    pub fn ranges<'a, D>(
-        &'a self,
-        snapshot: &'a MultiBufferSnapshot,
-    ) -> impl Iterator<Item = (Range<D>, &'a T)> + 'a
-    where
-        D: TextDimension + Clone,
-    {
-        let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>();
-        self.child_maps
-            .iter()
-            .filter_map(move |(excerpt_id, text_map)| {
-                cursor.seek_forward(excerpt_id, Bias::Left, &());
-                if let Some(excerpt) = cursor.item() {
-                    if excerpt.id == *excerpt_id {
-                        let mut excerpt_start = D::from_text_summary(&cursor.start().text);
-                        excerpt_start.add_summary(&excerpt.header_summary(), &());
-                        return Some(text_map.ranges::<D>(&excerpt.buffer).map(
-                            move |(range, value)| {
-                                let mut full_range = excerpt_start.clone()..excerpt_start.clone();
-                                full_range.start.add_assign(&range.start);
-                                full_range.end.add_assign(&range.end);
-                                (full_range, value)
-                            },
-                        ));
-                    }
-                }
-                None
-            })
-            .flatten()
-    }
-
-    pub fn intersecting_ranges<'a, D, I>(
-        &'a self,
-        range: Range<(I, Bias)>,
-        snapshot: &'a MultiBufferSnapshot,
-    ) -> impl Iterator<Item = (Range<D>, &'a T)> + 'a
-    where
-        D: TextDimension,
-        I: ToOffset,
-    {
-        let start_bias = range.start.1;
-        let end_bias = range.end.1;
-        let start_offset = range.start.0.to_offset(snapshot);
-        let end_offset = range.end.0.to_offset(snapshot);
-
-        let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>();
-        cursor.seek(&start_offset, start_bias, &());
-        let start_excerpt_id = &cursor.start().excerpt_id;
-        let start_ix = match self
-            .child_maps
-            .binary_search_by_key(&start_excerpt_id, |e| &e.0)
-        {
-            Ok(ix) | Err(ix) => ix,
-        };
-
-        let mut entry_ranges = None;
-        let mut entries = self.child_maps[start_ix..].iter();
-        std::iter::from_fn(move || loop {
-            match &mut entry_ranges {
-                None => {
-                    let (excerpt_id, text_map) = entries.next()?;
-                    cursor.seek(excerpt_id, Bias::Left, &());
-                    if cursor.start().text.bytes >= end_offset {
-                        return None;
-                    }
-
-                    if let Some(excerpt) = cursor.item() {
-                        if excerpt.id == *excerpt_id {
-                            let mut excerpt_start = D::from_text_summary(&cursor.start().text);
-                            excerpt_start.add_summary(&excerpt.header_summary(), &());
-
-                            let excerpt_start_offset = cursor.start().text.bytes;
-                            let excerpt_end_offset = cursor.end(&()).text.bytes;
-                            let excerpt_buffer_range = excerpt.range.to_offset(&excerpt.buffer);
-
-                            let start;
-                            if start_offset >= excerpt_start_offset {
-                                start = (
-                                    excerpt_buffer_range.start + start_offset
-                                        - excerpt_start_offset,
-                                    start_bias,
-                                );
-                            } else {
-                                start = (excerpt_buffer_range.start, Bias::Left);
-                            }
-
-                            let end;
-                            if end_offset <= excerpt_end_offset {
-                                end = (
-                                    excerpt_buffer_range.start + end_offset - excerpt_start_offset,
-                                    end_bias,
-                                );
-                            } else {
-                                end = (excerpt_buffer_range.end, Bias::Right);
-                            }
-
-                            entry_ranges = Some(
-                                text_map
-                                    .intersecting_ranges(start..end, &excerpt.buffer)
-                                    .map(move |(range, value)| {
-                                        let mut full_range =
-                                            excerpt_start.clone()..excerpt_start.clone();
-                                        full_range.start.add_assign(&range.start);
-                                        full_range.end.add_assign(&range.end);
-                                        (full_range, value)
-                                    }),
-                            );
-                        }
-                    }
-                }
-                Some(ranges) => {
-                    if let Some(item) = ranges.next() {
-                        return Some(item);
-                    } else {
-                        entry_ranges.take();
-                    }
-                }
-            }
-        })
-    }
-
-    pub fn min_by_key<'a, D, F, K>(
-        &self,
-        snapshot: &'a MultiBufferSnapshot,
-        extract_key: F,
-    ) -> Option<(Range<D>, &T)>
-    where
-        D: TextDimension,
-        F: FnMut(&T) -> K,
-        K: Ord,
-    {
-        self.min_or_max_by_key(snapshot, Ordering::Less, extract_key)
-    }
-
-    pub fn max_by_key<'a, D, F, K>(
-        &self,
-        snapshot: &'a MultiBufferSnapshot,
-        extract_key: F,
-    ) -> Option<(Range<D>, &T)>
-    where
-        D: TextDimension,
-        F: FnMut(&T) -> K,
-        K: Ord,
-    {
-        self.min_or_max_by_key(snapshot, Ordering::Greater, extract_key)
-    }
-
-    fn min_or_max_by_key<'a, D, F, K>(
-        &self,
-        snapshot: &'a MultiBufferSnapshot,
-        target_ordering: Ordering,
-        mut extract_key: F,
-    ) -> Option<(Range<D>, &T)>
-    where
-        D: TextDimension,
-        F: FnMut(&T) -> K,
-        K: Ord,
-    {
-        let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>();
-        let mut max = None;
-        for (excerpt_id, text_map) in &self.child_maps {
-            cursor.seek(excerpt_id, Bias::Left, &());
-            if let Some(excerpt) = cursor.item() {
-                if excerpt.id == *excerpt_id {
-                    if let Some((range, value)) =
-                        text_map.max_by_key(&excerpt.buffer, &mut extract_key)
-                    {
-                        if max.as_ref().map_or(true, |(_, max_value)| {
-                            extract_key(value).cmp(&extract_key(*max_value)) == target_ordering
-                        }) {
-                            let mut excerpt_start = D::from_text_summary(&cursor.start().text);
-                            excerpt_start.add_summary(&excerpt.header_summary(), &());
-                            let mut full_range = excerpt_start.clone()..excerpt_start.clone();
-                            full_range.start.add_assign(&range.start);
-                            full_range.end.add_assign(&range.end);
-                            max = Some((full_range, value));
-                        }
-                    }
-                }
-            }
-        }
-        max
-    }
-}
-
-impl AnchorRangeSet {
-    pub fn len(&self) -> usize {
-        self.0.len()
-    }
-
-    pub fn ranges<'a, D>(
-        &'a self,
-        content: &'a MultiBufferSnapshot,
-    ) -> impl 'a + Iterator<Item = Range<Point>>
-    where
-        D: TextDimension,
-    {
-        self.0.ranges(content).map(|(range, _)| range)
+        snapshot.summary_for_anchor(self)
     }
 }
 

crates/language/src/multi_buffer/location.rs 🔗

@@ -1,76 +0,0 @@
-use smallvec::{smallvec, SmallVec};
-use std::iter;
-
-pub type ExcerptId = Location;
-
-#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct Location(SmallVec<[u8; 4]>);
-
-impl Location {
-    pub fn min() -> Self {
-        Self(smallvec![u8::MIN])
-    }
-
-    pub fn max() -> Self {
-        Self(smallvec![u8::MAX])
-    }
-
-    pub fn between(lhs: &Self, rhs: &Self) -> Self {
-        let lhs = lhs.0.iter().copied().chain(iter::repeat(u8::MIN));
-        let rhs = rhs.0.iter().copied().chain(iter::repeat(u8::MAX));
-        let mut location = SmallVec::new();
-        for (lhs, rhs) in lhs.zip(rhs) {
-            let mid = lhs + (rhs.saturating_sub(lhs)) / 2;
-            location.push(mid);
-            if mid > lhs {
-                break;
-            }
-        }
-        Self(location)
-    }
-}
-
-impl Default for Location {
-    fn default() -> Self {
-        Self::min()
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use rand::prelude::*;
-    use std::mem;
-
-    #[gpui::test(iterations = 100)]
-    fn test_location(mut rng: StdRng) {
-        let mut lhs = Default::default();
-        let mut rhs = Default::default();
-        while lhs == rhs {
-            lhs = Location(
-                (0..rng.gen_range(1..=5))
-                    .map(|_| rng.gen_range(0..=100))
-                    .collect(),
-            );
-            rhs = Location(
-                (0..rng.gen_range(1..=5))
-                    .map(|_| rng.gen_range(0..=100))
-                    .collect(),
-            );
-        }
-
-        if lhs > rhs {
-            mem::swap(&mut lhs, &mut rhs);
-        }
-
-        let middle = Location::between(&lhs, &rhs);
-        assert!(middle > lhs);
-        assert!(middle < rhs);
-        for ix in 0..middle.0.len() - 1 {
-            assert!(
-                middle.0[ix] == *lhs.0.get(ix).unwrap_or(&0)
-                    || middle.0[ix] == *rhs.0.get(ix).unwrap_or(&0)
-            );
-        }
-    }
-}

crates/language/src/multi_buffer/selection.rs 🔗

@@ -1,13 +1,16 @@
-use super::{anchor::AnchorRangeMap, MultiBufferSnapshot, ToOffset};
-use std::{ops::Range, sync::Arc};
+use super::{Anchor, MultiBufferSnapshot, ToOffset};
+use std::{
+    ops::{Range, Sub},
+    sync::Arc,
+};
 use sum_tree::Bias;
-use text::{rope::TextDimension, Selection, SelectionSetId, SelectionState};
+use text::{rope::TextDimension, Selection, SelectionSetId};
 
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct SelectionSet {
     pub id: SelectionSetId,
     pub active: bool,
-    pub selections: Arc<AnchorRangeMap<SelectionState>>,
+    pub selections: Arc<[Selection<Anchor>]>,
 }
 
 impl SelectionSet {
@@ -17,75 +20,102 @@ impl SelectionSet {
 
     pub fn selections<'a, D>(
         &'a self,
-        content: &'a MultiBufferSnapshot,
+        snapshot: &'a MultiBufferSnapshot,
     ) -> impl 'a + Iterator<Item = Selection<D>>
     where
-        D: TextDimension,
+        D: TextDimension + Ord + Sub<D, Output = D>,
     {
-        self.selections
-            .ranges(content)
-            .map(|(range, state)| Selection {
-                id: state.id,
-                start: range.start,
-                end: range.end,
-                reversed: state.reversed,
-                goal: state.goal,
-            })
+        resolve_selections(&self.selections, snapshot)
     }
 
     pub fn intersecting_selections<'a, D, I>(
         &'a self,
         range: Range<(I, Bias)>,
-        content: &'a MultiBufferSnapshot,
+        snapshot: &'a MultiBufferSnapshot,
     ) -> impl 'a + Iterator<Item = Selection<D>>
     where
-        D: TextDimension,
+        D: TextDimension + Ord + Sub<D, Output = D>,
         I: 'a + ToOffset,
     {
-        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,
-            })
+        let start = snapshot.anchor_at(range.start.0, range.start.1);
+        let end = snapshot.anchor_at(range.end.0, range.end.1);
+        let start_ix = match self
+            .selections
+            .binary_search_by(|probe| probe.end.cmp(&start, snapshot).unwrap())
+        {
+            Ok(ix) | Err(ix) => ix,
+        };
+        let end_ix = match self
+            .selections
+            .binary_search_by(|probe| probe.start.cmp(&end, snapshot).unwrap())
+        {
+            Ok(ix) | Err(ix) => ix,
+        };
+        resolve_selections(&self.selections[start_ix..end_ix], snapshot)
     }
 
     pub fn oldest_selection<'a, D>(
         &'a self,
-        content: &'a MultiBufferSnapshot,
+        snapshot: &'a MultiBufferSnapshot,
     ) -> Option<Selection<D>>
     where
-        D: TextDimension,
+        D: TextDimension + Ord + Sub<D, Output = D>,
     {
         self.selections
-            .min_by_key(content, |selection| selection.id)
-            .map(|(range, state)| Selection {
-                id: state.id,
-                start: range.start,
-                end: range.end,
-                reversed: state.reversed,
-                goal: state.goal,
-            })
+            .iter()
+            .min_by_key(|selection| selection.id)
+            .map(|selection| resolve_selection(selection, snapshot))
     }
 
     pub fn newest_selection<'a, D>(
         &'a self,
-        content: &'a MultiBufferSnapshot,
+        snapshot: &'a MultiBufferSnapshot,
     ) -> Option<Selection<D>>
     where
-        D: TextDimension,
+        D: TextDimension + Ord + Sub<D, Output = D>,
     {
         self.selections
-            .max_by_key(content, |selection| selection.id)
-            .map(|(range, state)| Selection {
-                id: state.id,
-                start: range.start,
-                end: range.end,
-                reversed: state.reversed,
-                goal: state.goal,
-            })
+            .iter()
+            .max_by_key(|selection| selection.id)
+            .map(|selection| resolve_selection(selection, snapshot))
     }
 }
+
+fn resolve_selection<'a, D>(
+    selection: &'a Selection<Anchor>,
+    snapshot: &'a MultiBufferSnapshot,
+) -> Selection<D>
+where
+    D: TextDimension + Ord + Sub<D, Output = D>,
+{
+    Selection {
+        id: selection.id,
+        start: selection.start.summary::<D>(snapshot),
+        end: selection.end.summary::<D>(snapshot),
+        reversed: selection.reversed,
+        goal: selection.goal,
+    }
+}
+
+fn resolve_selections<'a, D>(
+    selections: &'a [Selection<Anchor>],
+    snapshot: &'a MultiBufferSnapshot,
+) -> impl 'a + Iterator<Item = Selection<D>>
+where
+    D: TextDimension + Ord + Sub<D, Output = D>,
+{
+    let mut summaries = snapshot
+        .summaries_for_anchors::<D, _>(
+            selections
+                .iter()
+                .flat_map(|selection| [&selection.start, &selection.end]),
+        )
+        .into_iter();
+    selections.iter().map(move |selection| Selection {
+        id: selection.id,
+        start: summaries.next().unwrap(),
+        end: summaries.next().unwrap(),
+        reversed: selection.reversed,
+        goal: selection.goal,
+    })
+}

crates/text/src/text.rs 🔗

@@ -1,5 +1,5 @@
 mod anchor;
-mod locator;
+pub mod locator;
 pub mod operation_queue;
 mod patch;
 mod point;