Add match associated type and fix failing test

K Simmons created

Change summary

crates/editor/src/items.rs         | 93 ++++++++++++-------------------
crates/search/src/buffer_search.rs | 28 ++++-----
crates/workspace/src/searchable.rs | 44 ++++++++++++---
3 files changed, 82 insertions(+), 83 deletions(-)

Detailed changes

crates/editor/src/items.rs 🔗

@@ -15,7 +15,6 @@ use rpc::proto::{self, update_view};
 use settings::Settings;
 use smallvec::SmallVec;
 use std::{
-    any::Any,
     borrow::Cow,
     cmp::{self, Ordering},
     fmt::Write,
@@ -510,6 +509,8 @@ impl ProjectItem for Editor {
 
 enum BufferSearchHighlights {}
 impl SearchableItem for Editor {
+    type Match = Range<Anchor>;
+
     fn to_search_event(event: &Self::Event) -> Option<SearchEvent> {
         match event {
             Event::BufferEdited => Some(SearchEvent::ContentsUpdated),
@@ -522,6 +523,14 @@ impl SearchableItem for Editor {
         self.clear_background_highlights::<BufferSearchHighlights>(cx);
     }
 
+    fn highlight_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) {
+        self.highlight_background::<BufferSearchHighlights>(
+            matches,
+            |theme| theme.search.match_background,
+            cx,
+        );
+    }
+
     fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
         let display_map = self.snapshot(cx).display_snapshot;
         let selection = self.selections.newest::<usize>(cx);
@@ -548,61 +557,40 @@ impl SearchableItem for Editor {
         &mut self,
         index: usize,
         direction: Direction,
-        matches: &Vec<Box<dyn Any + Send>>,
+        matches: Vec<Range<Anchor>>,
         cx: &mut ViewContext<Self>,
     ) {
-        if let Some(matches) = matches
-            .iter()
-            .map(|range| range.downcast_ref::<Range<Anchor>>().cloned())
-            .collect::<Option<Vec<_>>>()
-        {
-            let new_index: usize = match_index_for_direction(
-                matches.as_slice(),
-                &self.selections.newest_anchor().head(),
-                index,
-                direction,
-                &self.buffer().read(cx).snapshot(cx),
-            );
-
-            let range_to_select = matches[new_index].clone();
-            self.unfold_ranges([range_to_select.clone()], false, cx);
-            self.change_selections(Some(Autoscroll::Fit), cx, |s| {
-                s.select_ranges([range_to_select])
-            });
-        } else {
-            log::error!("Select next match in direction called with unexpected type matches");
-        }
+        let new_index: usize = match_index_for_direction(
+            matches.as_slice(),
+            &self.selections.newest_anchor().head(),
+            index,
+            direction,
+            &self.buffer().read(cx).snapshot(cx),
+        );
+
+        let range_to_select = matches[new_index].clone();
+        self.unfold_ranges([range_to_select.clone()], false, cx);
+        self.change_selections(Some(Autoscroll::Fit), cx, |s| {
+            s.select_ranges([range_to_select])
+        });
     }
 
     fn select_match_by_index(
         &mut self,
         index: usize,
-        matches: &Vec<Box<dyn Any + Send>>,
+        matches: Vec<Range<Anchor>>,
         cx: &mut ViewContext<Self>,
     ) {
-        if let Some(matches) = matches
-            .iter()
-            .map(|range| range.downcast_ref::<Range<Anchor>>().cloned())
-            .collect::<Option<Vec<_>>>()
-        {
-            self.change_selections(Some(Autoscroll::Fit), cx, |s| {
-                s.select_ranges([matches[index].clone()])
-            });
-            self.highlight_background::<BufferSearchHighlights>(
-                matches,
-                |theme| theme.search.match_background,
-                cx,
-            );
-        } else {
-            log::error!("Select next match in direction called with unexpected type matches");
-        }
+        self.change_selections(Some(Autoscroll::Fit), cx, |s| {
+            s.select_ranges([matches[index].clone()])
+        });
     }
 
     fn matches(
         &mut self,
         query: project::search::SearchQuery,
         cx: &mut ViewContext<Self>,
-    ) -> Task<Vec<Box<dyn Any + Send>>> {
+    ) -> Task<Vec<Range<Anchor>>> {
         let buffer = self.buffer().read(cx).snapshot(cx);
         cx.background().spawn(async move {
             let mut ranges = Vec::new();
@@ -633,30 +621,19 @@ impl SearchableItem for Editor {
                 }
             }
             ranges
-                .into_iter()
-                .map::<Box<dyn Any + Send>, _>(|range| Box::new(range))
-                .collect()
         })
     }
 
     fn active_match_index(
         &mut self,
-        matches: &Vec<Box<dyn Any + Send>>,
+        matches: Vec<Range<Anchor>>,
         cx: &mut ViewContext<Self>,
     ) -> Option<usize> {
-        if let Some(matches) = matches
-            .iter()
-            .map(|range| range.downcast_ref::<Range<Anchor>>().cloned())
-            .collect::<Option<Vec<_>>>()
-        {
-            active_match_index(
-                &matches,
-                &self.selections.newest_anchor().head(),
-                &self.buffer().read(cx).snapshot(cx),
-            )
-        } else {
-            None
-        }
+        active_match_index(
+            &matches,
+            &self.selections.newest_anchor().head(),
+            &self.buffer().read(cx).snapshot(cx),
+        )
     }
 }
 

crates/search/src/buffer_search.rs 🔗

@@ -421,6 +421,7 @@ impl BufferSearchBar {
                     .get(&searchable_item.downgrade())
                 {
                     searchable_item.select_next_match_in_direction(index, direction, matches, cx);
+                    searchable_item.highlight_matches(matches, cx);
                 }
             }
         }
@@ -468,18 +469,13 @@ impl BufferSearchBar {
     }
 
     fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
-        let mut active_editor_matches = None;
+        let mut active_item_matches = None;
         for (searchable_item, matches) in self.seachable_items_with_matches.drain() {
             if let Some(searchable_item) =
                 WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx)
             {
-                if self
-                    .active_searchable_item
-                    .as_ref()
-                    .map(|active_item| active_item == &searchable_item)
-                    .unwrap_or(false)
-                {
-                    active_editor_matches = Some((searchable_item.downgrade(), matches));
+                if Some(&searchable_item) == self.active_searchable_item.as_ref() {
+                    active_item_matches = Some((searchable_item.downgrade(), matches));
                 } else {
                     searchable_item.clear_highlights(cx);
                 }
@@ -487,7 +483,7 @@ impl BufferSearchBar {
         }
 
         self.seachable_items_with_matches
-            .extend(active_editor_matches);
+            .extend(active_item_matches);
     }
 
     fn update_matches(&mut self, select_closest_match: bool, cx: &mut ViewContext<Self>) {
@@ -527,17 +523,17 @@ impl BufferSearchBar {
 
                                 this.update_match_index(cx);
                                 if !this.dismissed {
+                                    let matches = this
+                                        .seachable_items_with_matches
+                                        .get(&active_searchable_item.downgrade())
+                                        .unwrap();
                                     if select_closest_match {
                                         if let Some(match_ix) = this.active_match_index {
-                                            active_searchable_item.select_match_by_index(
-                                                match_ix,
-                                                this.seachable_items_with_matches
-                                                    .get(&active_searchable_item.downgrade())
-                                                    .unwrap(),
-                                                cx,
-                                            );
+                                            active_searchable_item
+                                                .select_match_by_index(match_ix, matches, cx);
                                         }
                                     }
+                                    active_searchable_item.highlight_matches(matches, cx);
                                 }
                                 cx.notify();
                             }

crates/workspace/src/searchable.rs 🔗

@@ -8,6 +8,7 @@ use project::search::SearchQuery;
 
 use crate::{Item, ItemHandle, WeakItemHandle};
 
+#[derive(Debug)]
 pub enum SearchEvent {
     ContentsUpdated,
     SelectionsChanged,
@@ -20,30 +21,30 @@ pub enum Direction {
 }
 
 pub trait SearchableItem: Item {
+    type Match: Any + Sync + Send + Clone;
+
     fn to_search_event(event: &Self::Event) -> Option<SearchEvent>;
     fn clear_highlights(&mut self, cx: &mut ViewContext<Self>);
+    fn highlight_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
     fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
     fn select_next_match_in_direction(
         &mut self,
         index: usize,
         direction: Direction,
-        matches: &Vec<Box<dyn Any + Send>>,
+        matches: Vec<Self::Match>,
         cx: &mut ViewContext<Self>,
     );
     fn select_match_by_index(
         &mut self,
         index: usize,
-        matches: &Vec<Box<dyn Any + Send>>,
+        matches: Vec<Self::Match>,
         cx: &mut ViewContext<Self>,
     );
-    fn matches(
-        &mut self,
-        query: SearchQuery,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Vec<Box<dyn Any + Send>>>;
+    fn matches(&mut self, query: SearchQuery, cx: &mut ViewContext<Self>)
+        -> Task<Vec<Self::Match>>;
     fn active_match_index(
         &mut self,
-        matches: &Vec<Box<dyn Any + Send>>,
+        matches: Vec<Self::Match>,
         cx: &mut ViewContext<Self>,
     ) -> Option<usize>;
 }
@@ -57,6 +58,7 @@ pub trait SearchableItemHandle: ItemHandle {
         handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>,
     ) -> Subscription;
     fn clear_highlights(&self, cx: &mut MutableAppContext);
+    fn highlight_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut MutableAppContext);
     fn query_suggestion(&self, cx: &mut MutableAppContext) -> String;
     fn select_next_match_in_direction(
         &self,
@@ -107,6 +109,10 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
     fn clear_highlights(&self, cx: &mut MutableAppContext) {
         self.update(cx, |this, cx| this.clear_highlights(cx));
     }
+    fn highlight_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut MutableAppContext) {
+        let matches = downcast_matches(matches);
+        self.update(cx, |this, cx| this.highlight_matches(matches, cx));
+    }
     fn query_suggestion(&self, cx: &mut MutableAppContext) -> String {
         self.update(cx, |this, cx| this.query_suggestion(cx))
     }
@@ -117,6 +123,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
         matches: &Vec<Box<dyn Any + Send>>,
         cx: &mut MutableAppContext,
     ) {
+        let matches = downcast_matches(matches);
         self.update(cx, |this, cx| {
             this.select_next_match_in_direction(index, direction, matches, cx)
         });
@@ -127,6 +134,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
         matches: &Vec<Box<dyn Any + Send>>,
         cx: &mut MutableAppContext,
     ) {
+        let matches = downcast_matches(matches);
         self.update(cx, |this, cx| {
             this.select_match_by_index(index, matches, cx)
         });
@@ -136,17 +144,35 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
         query: SearchQuery,
         cx: &mut MutableAppContext,
     ) -> Task<Vec<Box<dyn Any + Send>>> {
-        self.update(cx, |this, cx| this.matches(query, cx))
+        let matches = self.update(cx, |this, cx| this.matches(query, cx));
+        cx.foreground().spawn(async {
+            let matches = matches.await;
+            matches
+                .into_iter()
+                .map::<Box<dyn Any + Send>, _>(|range| Box::new(range))
+                .collect()
+        })
     }
     fn active_match_index(
         &self,
         matches: &Vec<Box<dyn Any + Send>>,
         cx: &mut MutableAppContext,
     ) -> Option<usize> {
+        let matches = downcast_matches(matches);
         self.update(cx, |this, cx| this.active_match_index(matches, cx))
     }
 }
 
+fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T> {
+    matches
+        .iter()
+        .map(|range| range.downcast_ref::<T>().cloned())
+        .collect::<Option<Vec<_>>>()
+        .expect(
+            "SearchableItemHandle function called with vec of matches of a different type than expected",
+        )
+}
+
 impl From<Box<dyn SearchableItemHandle>> for AnyViewHandle {
     fn from(this: Box<dyn SearchableItemHandle>) -> Self {
         this.to_any()