mac: Delay initial find pasteboard search until ⌘G or ⌘F (#45605)

Agus Zubiaga created

Follow up to https://github.com/zed-industries/zed/pull/45311. Instead
of searching for the string in the find pasteboard as soon as the pane
is focused, we will now wait until the search bar is either deployed or
`Select{Next|Prev}Match` is triggered.

Release Notes:

- N/A

Change summary

crates/search/src/buffer_search.rs           | 97 +++++++++++++++------
crates/search/src/buffer_search/registrar.rs | 18 ++-
2 files changed, 79 insertions(+), 36 deletions(-)

Detailed changes

crates/search/src/buffer_search.rs 🔗

@@ -4,6 +4,7 @@ use crate::{
     FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOption,
     SearchOptions, SearchSource, SelectAllMatches, SelectNextMatch, SelectPreviousMatch,
     ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord,
+    buffer_search::registrar::WithResultsOrExternalQuery,
     search_bar::{ActionButtonState, input_base_styles, render_action_button, render_text_input},
 };
 use any_vec::AnyVec;
@@ -43,7 +44,7 @@ use workspace::{
 };
 
 pub use registrar::DivRegistrar;
-use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults};
+use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar};
 
 const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
 
@@ -110,6 +111,8 @@ pub struct BufferSearchBar {
     active_searchable_item_subscriptions: Option<[Subscription; 2]>,
     #[cfg(not(target_os = "macos"))]
     active_searchable_item_subscriptions: Option<Subscription>,
+    #[cfg(target_os = "macos")]
+    pending_external_query: Option<(String, SearchOptions)>,
     active_search: Option<Arc<SearchQuery>>,
     searchable_items_with_matches: HashMap<Box<dyn WeakSearchableItemHandle>, AnyVec<dyn Send>>,
     pending_search: Option<Task<()>>,
@@ -510,24 +513,27 @@ impl ToolbarItemView for BufferSearchBar {
                         }
 
                         cx.defer_in(window, |this, window, cx| {
-                            if let Some(item) = cx.read_from_find_pasteboard()
-                                && let Some(text) = item.text()
-                            {
-                                if this.query(cx) != text {
-                                    let search_options = item
-                                        .metadata()
-                                        .and_then(|m| m.parse().ok())
-                                        .and_then(SearchOptions::from_bits)
-                                        .unwrap_or(this.search_options);
-
-                                    drop(this.search(
-                                        &text,
-                                        Some(search_options),
-                                        true,
-                                        window,
-                                        cx,
-                                    ));
-                                }
+                            let Some(item) = cx.read_from_find_pasteboard() else {
+                                return;
+                            };
+                            let Some(text) = item.text() else {
+                                return;
+                            };
+
+                            if this.query(cx) == text {
+                                return;
+                            }
+
+                            let search_options = item
+                                .metadata()
+                                .and_then(|m| m.parse().ok())
+                                .and_then(SearchOptions::from_bits)
+                                .unwrap_or(this.search_options);
+
+                            if this.dismissed {
+                                this.pending_external_query = Some((text, search_options));
+                            } else {
+                                drop(this.search(&text, Some(search_options), true, window, cx));
                             }
                         });
                     }),
@@ -594,14 +600,16 @@ impl BufferSearchBar {
                 cx.propagate();
             }
         }));
-        registrar.register_handler(WithResults(|this, action: &SelectNextMatch, window, cx| {
-            if this.supported_options(cx).find_in_results {
-                cx.propagate();
-            } else {
-                this.select_next_match(action, window, cx);
-            }
-        }));
-        registrar.register_handler(WithResults(
+        registrar.register_handler(WithResultsOrExternalQuery(
+            |this, action: &SelectNextMatch, window, cx| {
+                if this.supported_options(cx).find_in_results {
+                    cx.propagate();
+                } else {
+                    this.select_next_match(action, window, cx);
+                }
+            },
+        ));
+        registrar.register_handler(WithResultsOrExternalQuery(
             |this, action: &SelectPreviousMatch, window, cx| {
                 if this.supported_options(cx).find_in_results {
                     cx.propagate();
@@ -610,7 +618,7 @@ impl BufferSearchBar {
                 }
             },
         ));
-        registrar.register_handler(WithResults(
+        registrar.register_handler(WithResultsOrExternalQuery(
             |this, action: &SelectAllMatches, window, cx| {
                 if this.supported_options(cx).find_in_results {
                     cx.propagate();
@@ -707,6 +715,8 @@ impl BufferSearchBar {
             replacement_editor_focused: false,
             active_searchable_item: None,
             active_searchable_item_subscriptions: None,
+            #[cfg(target_os = "macos")]
+            pending_external_query: None,
             active_match_index: None,
             searchable_items_with_matches: Default::default(),
             default_options: search_options,
@@ -852,11 +862,20 @@ impl BufferSearchBar {
             self.search(&suggestion, Some(self.default_options), true, window, cx)
         });
 
+        #[cfg(target_os = "macos")]
+        let search = search.or_else(|| {
+            self.pending_external_query
+                .take()
+                .map(|(query, options)| self.search(&query, Some(options), true, window, cx))
+        });
+
         if let Some(search) = search {
             cx.spawn_in(window, async move |this, cx| {
                 if search.await.is_ok() {
                     this.update_in(cx, |this, window, cx| {
-                        this.activate_current_match(window, cx)
+                        if !this.dismissed {
+                            this.activate_current_match(window, cx)
+                        }
                     })
                 } else {
                     Ok(())
@@ -1071,6 +1090,22 @@ impl BufferSearchBar {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
+        #[cfg(target_os = "macos")]
+        if let Some((query, options)) = self.pending_external_query.take() {
+            let search_rx = self.search(&query, Some(options), true, window, cx);
+            cx.spawn_in(window, async move |this, cx| {
+                if search_rx.await.is_ok() {
+                    this.update_in(cx, |this, window, cx| {
+                        this.activate_current_match(window, cx);
+                    })
+                    .ok();
+                }
+            })
+            .detach();
+
+            return;
+        }
+
         if let Some(index) = self.active_match_index
             && let Some(searchable_item) = self.active_searchable_item.as_ref()
             && let Some(matches) = self
@@ -1271,6 +1306,8 @@ impl BufferSearchBar {
         let (done_tx, done_rx) = oneshot::channel();
         let query = self.query(cx);
         self.pending_search.take();
+        #[cfg(target_os = "macos")]
+        self.pending_external_query.take();
 
         if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
             self.query_error = None;
@@ -1367,8 +1404,8 @@ impl BufferSearchBar {
                                         cx,
                                     );
                                 }
-                                let _ = done_tx.send(());
                             }
+                            let _ = done_tx.send(());
                             cx.notify();
                         }
                     })

crates/search/src/buffer_search/registrar.rs 🔗

@@ -149,16 +149,16 @@ impl<A: Action> ActionExecutor<A> for ForDeployed<A> {
     }
 }
 
-/// Run an action when the search bar has any matches, regardless of whether it
-/// is visible or not.
-pub struct WithResults<A>(pub(super) SearchBarActionCallback<A>);
-impl<A> Clone for WithResults<A> {
+/// Run an action when the search bar has any matches or a pending external query,
+/// regardless of whether it is visible or not.
+pub struct WithResultsOrExternalQuery<A>(pub(super) SearchBarActionCallback<A>);
+impl<A> Clone for WithResultsOrExternalQuery<A> {
     fn clone(&self) -> Self {
         Self(self.0)
     }
 }
 
-impl<A: Action> ActionExecutor<A> for WithResults<A> {
+impl<A: Action> ActionExecutor<A> for WithResultsOrExternalQuery<A> {
     fn execute(
         &self,
         search_bar: &mut BufferSearchBar,
@@ -166,7 +166,13 @@ impl<A: Action> ActionExecutor<A> for WithResults<A> {
         window: &mut Window,
         cx: &mut Context<BufferSearchBar>,
     ) -> DidHandleAction {
-        if search_bar.active_match_index.is_some() {
+        #[cfg(not(target_os = "macos"))]
+        let has_external_query = false;
+
+        #[cfg(target_os = "macos")]
+        let has_external_query = search_bar.pending_external_query.is_some();
+
+        if has_external_query || search_bar.active_match_index.is_some() {
             self.0(search_bar, action, window, cx);
             true
         } else {