Refactor retrieving oldest and newest selection

Antonio Scandurra and Nathan Sobo created

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

Change summary

crates/buffer/src/anchor.rs    | 60 ++++++++++++++++++++++++++
crates/buffer/src/selection.rs | 32 ++++++++++++++
crates/editor/src/lib.rs       | 81 ++++++++++++++++++++++++-----------
crates/workspace/src/items.rs  |  6 --
4 files changed, 149 insertions(+), 30 deletions(-)

Detailed changes

crates/buffer/src/anchor.rs 🔗

@@ -194,6 +194,66 @@ impl<T> AnchorRangeMap<T> {
             .iter()
             .map(|(range, value)| (range.start.0..range.end.0, value))
     }
+
+    pub fn min_by_key<'a, C, D, F, K>(
+        &self,
+        content: C,
+        mut extract_key: F,
+    ) -> Option<(Range<D>, &T)>
+    where
+        C: Into<Content<'a>>,
+        D: 'a + TextDimension<'a>,
+        F: FnMut(&T) -> K,
+        K: Ord,
+    {
+        let content = content.into();
+        self.entries
+            .iter()
+            .min_by_key(|(_, value)| extract_key(value))
+            .map(|(range, value)| (self.resolve_range(range, &content), value))
+    }
+
+    pub fn max_by_key<'a, C, D, F, K>(
+        &self,
+        content: C,
+        mut extract_key: F,
+    ) -> Option<(Range<D>, &T)>
+    where
+        C: Into<Content<'a>>,
+        D: 'a + TextDimension<'a>,
+        F: FnMut(&T) -> K,
+        K: Ord,
+    {
+        let content = content.into();
+        self.entries
+            .iter()
+            .max_by_key(|(_, value)| extract_key(value))
+            .map(|(range, value)| (self.resolve_range(range, &content), value))
+    }
+
+    fn resolve_range<'a, D>(
+        &self,
+        range: &Range<(FullOffset, Bias)>,
+        content: &Content<'a>,
+    ) -> Range<D>
+    where
+        D: 'a + TextDimension<'a>,
+    {
+        let (start, start_bias) = range.start;
+        let mut anchor = Anchor {
+            full_offset: start,
+            bias: start_bias,
+            version: self.version.clone(),
+        };
+        let start = content.summary_for_anchor(&anchor);
+
+        let (end, end_bias) = range.end;
+        anchor.full_offset = end;
+        anchor.bias = end_bias;
+        let end = content.summary_for_anchor(&anchor);
+
+        start..end
+    }
 }
 
 impl<T: PartialEq> PartialEq for AnchorRangeMap<T> {

crates/buffer/src/selection.rs 🔗

@@ -116,4 +116,36 @@ impl SelectionSet {
                 goal: state.goal,
             })
     }
+
+    pub fn oldest_selection<'a, D, C>(&'a self, content: C) -> Option<Selection<D>>
+    where
+        D: 'a + TextDimension<'a>,
+        C: 'a + Into<Content<'a>>,
+    {
+        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,
+            })
+    }
+
+    pub fn newest_selection<'a, D, C>(&'a self, content: C) -> Option<Selection<D>>
+    where
+        D: 'a + TextDimension<'a>,
+        C: 'a + Into<Content<'a>>,
+    {
+        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,
+            })
+    }
 }

crates/editor/src/lib.rs 🔗

@@ -707,16 +707,8 @@ impl Editor {
                 self.update_selections(vec![pending_selection], true, cx);
             }
         } else {
-            let selections = self.selections::<Point>(cx);
-            let mut selection_count = 0;
-            let mut oldest_selection = selections
-                .min_by_key(|s| {
-                    selection_count += 1;
-                    s.id
-                })
-                .unwrap()
-                .clone();
-            if selection_count == 1 {
+            let mut oldest_selection = self.oldest_selection::<usize>(cx);
+            if self.selection_count(cx) == 1 {
                 oldest_selection.start = oldest_selection.head().clone();
                 oldest_selection.end = oldest_selection.head().clone();
             }
@@ -2294,9 +2286,8 @@ impl Editor {
     ) -> impl 'a + Iterator<Item = Range<DisplayPoint>> {
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = self.buffer.read(cx);
-        let selections = buffer
-            .selection_set(set_id)
-            .unwrap()
+        let selections = self
+            .selection_set(cx)
             .selections::<Point, _>(buffer)
             .collect::<Vec<_>>();
         let start = range.start.to_point(&display_map);
@@ -2343,18 +2334,8 @@ impl Editor {
         D: 'a + TextDimension<'a> + Ord,
     {
         let buffer = self.buffer.read(cx);
-        let mut selections = buffer
-            .selection_set(self.selection_set_id)
-            .unwrap()
-            .selections::<D, _>(buffer)
-            .peekable();
-        let mut pending_selection = self.pending_selection.clone().map(|selection| Selection {
-            id: selection.id,
-            start: selection.start.summary::<D, _>(buffer),
-            end: selection.end.summary::<D, _>(buffer),
-            reversed: selection.reversed,
-            goal: selection.goal,
-        });
+        let mut selections = self.selection_set(cx).selections::<D, _>(buffer).peekable();
+        let mut pending_selection = self.pending_selection(cx);
         iter::from_fn(move || {
             if let Some(pending) = pending_selection.as_mut() {
                 while let Some(next_selection) = selections.peek() {
@@ -2380,6 +2361,56 @@ impl Editor {
         })
     }
 
+    fn pending_selection<'a, D>(&self, cx: &'a AppContext) -> Option<Selection<D>>
+    where
+        D: 'a + TextDimension<'a>,
+    {
+        let buffer = self.buffer.read(cx);
+        self.pending_selection.as_ref().map(|selection| Selection {
+            id: selection.id,
+            start: selection.start.summary::<D, _>(buffer),
+            end: selection.end.summary::<D, _>(buffer),
+            reversed: selection.reversed,
+            goal: selection.goal,
+        })
+    }
+
+    fn selection_count<'a>(&self, cx: &'a AppContext) -> usize {
+        let mut selection_count = self.selection_set(cx).len();
+        if self.pending_selection.is_some() {
+            selection_count += 1;
+        }
+        selection_count
+    }
+
+    pub fn oldest_selection<'a, T>(&self, cx: &'a AppContext) -> Selection<T>
+    where
+        T: 'a + TextDimension<'a>,
+    {
+        let buffer = self.buffer.read(cx);
+        self.selection_set(cx)
+            .oldest_selection(buffer)
+            .or_else(|| self.pending_selection(cx))
+            .unwrap()
+    }
+
+    pub fn newest_selection<'a, T>(&self, cx: &'a AppContext) -> Selection<T>
+    where
+        T: 'a + TextDimension<'a>,
+    {
+        let buffer = self.buffer.read(cx);
+        self.pending_selection(cx)
+            .or_else(|| self.selection_set(cx).newest_selection(buffer))
+            .unwrap()
+    }
+
+    fn selection_set<'a>(&self, cx: &'a AppContext) -> &'a SelectionSet {
+        self.buffer
+            .read(cx)
+            .selection_set(self.selection_set_id)
+            .unwrap()
+    }
+
     fn update_selections<T>(
         &mut self,
         mut selections: Vec<Selection<T>>,

crates/workspace/src/items.rs 🔗

@@ -258,11 +258,7 @@ impl DiagnosticMessage {
 
     fn update(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
         let editor = editor.read(cx);
-        let cursor_position = editor
-            .selections::<usize>(cx)
-            .max_by_key(|selection| selection.id)
-            .unwrap()
-            .head();
+        let cursor_position = editor.newest_selection(cx).head();
         let new_diagnostic = editor
             .buffer()
             .read(cx)