Maintain search results as query and active editor changes

Antonio Scandurra and Nathan Sobo created

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

Change summary

Cargo.lock              |  1 
crates/find/Cargo.toml  |  3 +
crates/find/src/find.rs | 57 +++++++++++++++++++++++++++++++++++++-----
crates/gpui/src/app.rs  | 16 ++++++++++++
4 files changed, 69 insertions(+), 8 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1724,6 +1724,7 @@ name = "find"
 version = "0.1.0"
 dependencies = [
  "aho-corasick",
+ "collections",
  "editor",
  "gpui",
  "postage",

crates/find/Cargo.toml 🔗

@@ -7,10 +7,11 @@ edition = "2021"
 path = "src/find.rs"
 
 [dependencies]
-aho-corasick = "0.7"
+collections = { path = "../collections" }
 editor = { path = "../editor" }
 gpui = { path = "../gpui" }
 theme = { path = "../theme" }
 workspace = { path = "../workspace" }
+aho-corasick = "0.7"
 postage = { version = "0.4.1", features = ["futures-traits"] }
 smol = { version = "1.2" }

crates/find/src/find.rs 🔗

@@ -1,8 +1,9 @@
 use aho_corasick::AhoCorasickBuilder;
+use collections::HashSet;
 use editor::{char_kind, Editor, EditorSettings};
 use gpui::{
-    action, elements::*, keymap::Binding, Entity, MutableAppContext, RenderContext, Task, View,
-    ViewContext, ViewHandle,
+    action, elements::*, keymap::Binding, Entity, MutableAppContext, RenderContext, Subscription,
+    Task, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use postage::watch;
 use smol::future::yield_now;
@@ -34,6 +35,8 @@ struct FindBar {
     settings: watch::Receiver<Settings>,
     query_editor: ViewHandle<Editor>,
     active_editor: Option<ViewHandle<Editor>>,
+    active_editor_subscription: Option<Subscription>,
+    highlighted_editors: HashSet<WeakViewHandle<Editor>>,
     pending_search: Option<Task<()>>,
     case_sensitive_mode: bool,
     whole_word_mode: bool,
@@ -85,8 +88,19 @@ impl Toolbar for FindBar {
         item: Option<Box<dyn ItemViewHandle>>,
         cx: &mut ViewContext<Self>,
     ) -> bool {
-        self.active_editor = item.and_then(|item| item.act_as::<Editor>(cx));
-        self.active_editor.is_some()
+        self.active_editor_subscription.take();
+        self.active_editor.take();
+        self.pending_search.take();
+
+        if let Some(editor) = item.and_then(|item| item.act_as::<Editor>(cx)) {
+            self.active_editor_subscription =
+                Some(cx.subscribe(&editor, Self::on_active_editor_event));
+            self.active_editor = Some(editor);
+            self.update_matches(cx);
+            true
+        } else {
+            false
+        }
     }
 }
 
@@ -114,6 +128,8 @@ impl FindBar {
         Self {
             query_editor,
             active_editor: None,
+            active_editor_subscription: None,
+            highlighted_editors: Default::default(),
             case_sensitive_mode: false,
             whole_word_mode: false,
             regex_mode: false,
@@ -171,23 +187,49 @@ impl FindBar {
     }
 
     fn toggle_mode(&mut self, ToggleMode(mode): &ToggleMode, cx: &mut ViewContext<Self>) {
-        eprintln!("TOGGLE MODE");
         let value = match mode {
             SearchMode::WholeWord => &mut self.whole_word_mode,
             SearchMode::CaseSensitive => &mut self.case_sensitive_mode,
             SearchMode::Regex => &mut self.regex_mode,
         };
         *value = !*value;
+        self.update_matches(cx);
         cx.notify();
     }
 
     fn on_query_editor_event(
         &mut self,
         _: ViewHandle<Editor>,
-        _: &editor::Event,
+        event: &editor::Event,
         cx: &mut ViewContext<Self>,
     ) {
-        self.update_matches(cx);
+        match event {
+            editor::Event::Edited => {
+                for editor in self.highlighted_editors.drain() {
+                    if let Some(editor) = editor.upgrade(cx) {
+                        if Some(&editor) != self.active_editor.as_ref() {
+                            editor.update(cx, |editor, cx| {
+                                editor.clear_highlighted_ranges::<Self>(cx)
+                            });
+                        }
+                    }
+                }
+                self.update_matches(cx);
+            }
+            _ => {}
+        }
+    }
+
+    fn on_active_editor_event(
+        &mut self,
+        _: ViewHandle<Editor>,
+        event: &editor::Event,
+        cx: &mut ViewContext<Self>,
+    ) {
+        match event {
+            editor::Event::Edited => self.update_matches(cx),
+            _ => {}
+        }
     }
 
     fn update_matches(&mut self, cx: &mut ViewContext<Self>) {
@@ -247,6 +289,7 @@ impl FindBar {
                     {
                         this.update(&mut cx, |this, cx| {
                             let theme = &this.settings.borrow().theme.find;
+                            this.highlighted_editors.insert(editor.downgrade());
                             editor.update(cx, |editor, cx| {
                                 editor.highlight_ranges::<Self>(ranges, theme.match_background, cx)
                             });

crates/gpui/src/app.rs 🔗

@@ -3246,6 +3246,7 @@ impl Drop for AnyModelHandle {
         self.ref_counts.lock().dec_model(self.model_id);
     }
 }
+
 pub struct WeakViewHandle<T> {
     window_id: usize,
     view_id: usize,
@@ -3288,6 +3289,21 @@ impl<T> Clone for WeakViewHandle<T> {
     }
 }
 
+impl<T> PartialEq for WeakViewHandle<T> {
+    fn eq(&self, other: &Self) -> bool {
+        self.window_id == other.window_id && self.view_id == other.view_id
+    }
+}
+
+impl<T> Eq for WeakViewHandle<T> {}
+
+impl<T> Hash for WeakViewHandle<T> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.window_id.hash(state);
+        self.view_id.hash(state);
+    }
+}
+
 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
 pub struct ElementStateId(usize, usize);