Properly register workspace handlers for search elements (#4100)

Kirill Bulatov created

Change summary

crates/assistant/src/assistant_panel.rs    |   2 
crates/search/src/buffer_search.rs         | 128 +++++++++++--
crates/search/src/project_search.rs        | 215 ++++++++++++++++++-----
crates/terminal_view/src/terminal_panel.rs |   2 
4 files changed, 277 insertions(+), 70 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -1148,7 +1148,7 @@ impl Render for AssistantPanel {
                     |panel, cx| panel.toolbar.read(cx).item_of_type::<BufferSearchBar>(),
                     cx,
                 );
-                BufferSearchBar::register_inner(&mut registrar);
+                BufferSearchBar::register(&mut registrar);
                 registrar.into_div()
             } else {
                 div()

crates/search/src/buffer_search.rs 🔗

@@ -429,6 +429,11 @@ pub trait SearchActionsRegistrar {
         &mut self,
         callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
     );
+
+    fn register_handler_for_dismissed_search<A: Action>(
+        &mut self,
+        callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+    );
 }
 
 type GetSearchBar<T> =
@@ -457,16 +462,62 @@ impl<'a, 'b, T: 'static> DivRegistrar<'a, 'b, T> {
 }
 
 impl<T: 'static> SearchActionsRegistrar for DivRegistrar<'_, '_, T> {
-    fn register_handler<A: gpui::Action>(
+    fn register_handler<A: Action>(
+        &mut self,
+        callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+    ) {
+        let getter = self.search_getter;
+        self.div = self.div.take().map(|div| {
+            div.on_action(self.cx.listener(move |this, action, cx| {
+                let should_notify = (getter)(this, cx)
+                    .clone()
+                    .map(|search_bar| {
+                        search_bar.update(cx, |search_bar, cx| {
+                            if search_bar.is_dismissed()
+                                || search_bar.active_searchable_item.is_none()
+                            {
+                                false
+                            } else {
+                                callback(search_bar, action, cx);
+                                true
+                            }
+                        })
+                    })
+                    .unwrap_or(false);
+                if should_notify {
+                    cx.notify();
+                } else {
+                    cx.propagate();
+                }
+            }))
+        });
+    }
+
+    fn register_handler_for_dismissed_search<A: Action>(
         &mut self,
         callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
     ) {
         let getter = self.search_getter;
         self.div = self.div.take().map(|div| {
             div.on_action(self.cx.listener(move |this, action, cx| {
-                (getter)(this, cx)
+                let should_notify = (getter)(this, cx)
                     .clone()
-                    .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx)));
+                    .map(|search_bar| {
+                        search_bar.update(cx, |search_bar, cx| {
+                            if search_bar.is_dismissed() {
+                                callback(search_bar, action, cx);
+                                true
+                            } else {
+                                false
+                            }
+                        })
+                    })
+                    .unwrap_or(false);
+                if should_notify {
+                    cx.notify();
+                } else {
+                    cx.propagate();
+                }
             }))
         });
     }
@@ -488,44 +539,86 @@ impl SearchActionsRegistrar for Workspace {
             pane.update(cx, move |this, cx| {
                 this.toolbar().update(cx, move |this, cx| {
                     if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
-                        search_bar.update(cx, move |this, cx| callback(this, action, cx));
-                        cx.notify();
+                        let should_notify = search_bar.update(cx, move |search_bar, cx| {
+                            if search_bar.is_dismissed()
+                                || search_bar.active_searchable_item.is_none()
+                            {
+                                false
+                            } else {
+                                callback(search_bar, action, cx);
+                                true
+                            }
+                        });
+                        if should_notify {
+                            cx.notify();
+                        } else {
+                            cx.propagate();
+                        }
+                    }
+                })
+            });
+        });
+    }
+
+    fn register_handler_for_dismissed_search<A: Action>(
+        &mut self,
+        callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+    ) {
+        self.register_action(move |workspace, action: &A, cx| {
+            if workspace.has_active_modal(cx) {
+                cx.propagate();
+                return;
+            }
+
+            let pane = workspace.active_pane();
+            pane.update(cx, move |this, cx| {
+                this.toolbar().update(cx, move |this, cx| {
+                    if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
+                        let should_notify = search_bar.update(cx, move |search_bar, cx| {
+                            if search_bar.is_dismissed() {
+                                callback(search_bar, action, cx);
+                                true
+                            } else {
+                                false
+                            }
+                        });
+                        if should_notify {
+                            cx.notify();
+                        } else {
+                            cx.propagate();
+                        }
                     }
                 })
             });
         });
     }
 }
+
 impl BufferSearchBar {
-    pub fn register_inner(registrar: &mut impl SearchActionsRegistrar) {
+    pub fn register(registrar: &mut impl SearchActionsRegistrar) {
         registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| {
             if this.supported_options().case {
                 this.toggle_case_sensitive(action, cx);
             }
         });
-
         registrar.register_handler(|this, action: &ToggleWholeWord, cx| {
             if this.supported_options().word {
                 this.toggle_whole_word(action, cx);
             }
         });
-
         registrar.register_handler(|this, action: &ToggleReplace, cx| {
             if this.supported_options().replacement {
                 this.toggle_replace(action, cx);
             }
         });
-
         registrar.register_handler(|this, _: &ActivateRegexMode, cx| {
             if this.supported_options().regex {
                 this.activate_search_mode(SearchMode::Regex, cx);
             }
         });
-
         registrar.register_handler(|this, _: &ActivateTextMode, cx| {
             this.activate_search_mode(SearchMode::Text, cx);
         });
-
         registrar.register_handler(|this, action: &CycleMode, cx| {
             if this.supported_options().regex {
                 // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting
@@ -533,7 +626,6 @@ impl BufferSearchBar {
                 this.cycle_mode(action, cx)
             }
         });
-
         registrar.register_handler(|this, action: &SelectNextMatch, cx| {
             this.select_next_match(action, cx);
         });
@@ -544,19 +636,13 @@ impl BufferSearchBar {
             this.select_all_matches(action, cx);
         });
         registrar.register_handler(|this, _: &editor::Cancel, cx| {
-            if this.dismissed {
-                cx.propagate();
-            } else {
-                this.dismiss(&Dismiss, cx);
-            }
+            this.dismiss(&Dismiss, cx);
         });
-        registrar.register_handler(|this, deploy, cx| {
+        registrar.register_handler_for_dismissed_search(|this, deploy, cx| {
             this.deploy(deploy, cx);
         })
     }
-    fn register(workspace: &mut Workspace) {
-        Self::register_inner(workspace);
-    }
+
     pub fn new(cx: &mut ViewContext<Self>) -> Self {
         let query_editor = cx.new_view(|cx| Editor::single_line(cx));
         cx.subscribe(&query_editor, Self::on_query_editor_event)

crates/search/src/project_search.rs 🔗

@@ -12,10 +12,10 @@ use editor::{
 };
 use editor::{EditorElement, EditorStyle};
 use gpui::{
-    actions, div, AnyElement, AnyView, AppContext, Context as _, Element, EntityId, EventEmitter,
-    FocusHandle, FocusableView, FontStyle, FontWeight, Hsla, InteractiveElement, IntoElement,
-    KeyContext, Model, ModelContext, ParentElement, PromptLevel, Render, SharedString, Styled,
-    Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakModel, WeakView,
+    actions, div, Action, AnyElement, AnyView, AppContext, Context as _, Element, EntityId,
+    EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Hsla, InteractiveElement,
+    IntoElement, KeyContext, Model, ModelContext, ParentElement, PromptLevel, Render, SharedString,
+    Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakModel, WeakView,
     WhiteSpace, WindowContext,
 };
 use menu::Confirm;
@@ -36,6 +36,7 @@ use std::{
     time::{Duration, Instant},
 };
 use theme::ThemeSettings;
+use workspace::{DeploySearch, NewSearch};
 
 use ui::{
     h_flex, prelude::*, v_flex, Icon, IconButton, IconName, Label, LabelCommon, LabelSize,
@@ -60,10 +61,64 @@ struct ActiveSettings(HashMap<WeakModel<Project>, ProjectSearchSettings>);
 pub fn init(cx: &mut AppContext) {
     cx.set_global(ActiveSettings::default());
     cx.observe_new_views(|workspace: &mut Workspace, _cx| {
-        workspace
-            .register_action(ProjectSearchView::new_search)
-            .register_action(ProjectSearchView::deploy_search)
-            .register_action(ProjectSearchBar::search_in_new);
+        register_workspace_action(workspace, move |search_bar, _: &ToggleFilters, cx| {
+            search_bar.toggle_filters(cx);
+        });
+        register_workspace_action(workspace, move |search_bar, _: &ToggleCaseSensitive, cx| {
+            search_bar.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
+        });
+        register_workspace_action(workspace, move |search_bar, _: &ToggleWholeWord, cx| {
+            search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
+        });
+        register_workspace_action(workspace, move |search_bar, action: &ToggleReplace, cx| {
+            search_bar.toggle_replace(action, cx)
+        });
+        register_workspace_action(workspace, move |search_bar, _: &ActivateRegexMode, cx| {
+            search_bar.activate_search_mode(SearchMode::Regex, cx)
+        });
+        register_workspace_action(workspace, move |search_bar, _: &ActivateTextMode, cx| {
+            search_bar.activate_search_mode(SearchMode::Text, cx)
+        });
+        register_workspace_action(
+            workspace,
+            move |search_bar, _: &ActivateSemanticMode, cx| {
+                search_bar.activate_search_mode(SearchMode::Semantic, cx)
+            },
+        );
+        register_workspace_action(workspace, move |search_bar, action: &CycleMode, cx| {
+            search_bar.cycle_mode(action, cx)
+        });
+        register_workspace_action(
+            workspace,
+            move |search_bar, action: &SelectNextMatch, cx| {
+                search_bar.select_next_match(action, cx)
+            },
+        );
+        register_workspace_action(
+            workspace,
+            move |search_bar, action: &SelectPrevMatch, cx| {
+                search_bar.select_prev_match(action, cx)
+            },
+        );
+
+        register_workspace_action_for_dismissed_search(
+            workspace,
+            move |workspace, action: &NewSearch, cx| {
+                ProjectSearchView::new_search(workspace, action, cx)
+            },
+        );
+        register_workspace_action_for_dismissed_search(
+            workspace,
+            move |workspace, action: &DeploySearch, cx| {
+                ProjectSearchView::deploy_search(workspace, action, cx)
+            },
+        );
+        register_workspace_action_for_dismissed_search(
+            workspace,
+            move |workspace, action: &SearchInNew, cx| {
+                ProjectSearchView::search_in_new(workspace, action, cx)
+            },
+        );
     })
     .detach();
 }
@@ -960,6 +1015,37 @@ impl ProjectSearchView {
         Self::existing_or_new_search(workspace, existing, cx)
     }
 
+    fn search_in_new(workspace: &mut Workspace, _: &SearchInNew, cx: &mut ViewContext<Workspace>) {
+        if let Some(search_view) = workspace
+            .active_item(cx)
+            .and_then(|item| item.downcast::<ProjectSearchView>())
+        {
+            let new_query = search_view.update(cx, |search_view, cx| {
+                let new_query = search_view.build_search_query(cx);
+                if new_query.is_some() {
+                    if let Some(old_query) = search_view.model.read(cx).active_query.clone() {
+                        search_view.query_editor.update(cx, |editor, cx| {
+                            editor.set_text(old_query.as_str(), cx);
+                        });
+                        search_view.search_options = SearchOptions::from_query(&old_query);
+                    }
+                }
+                new_query
+            });
+            if let Some(new_query) = new_query {
+                let model = cx.new_model(|cx| {
+                    let mut model = ProjectSearch::new(workspace.project().clone(), cx);
+                    model.search(new_query, cx);
+                    model
+                });
+                workspace.add_item(
+                    Box::new(cx.new_view(|cx| ProjectSearchView::new(model, cx, None))),
+                    cx,
+                );
+            }
+        }
+    }
+
     // Add another search tab to the workspace.
     fn new_search(
         workspace: &mut Workspace,
@@ -1262,17 +1348,11 @@ impl ProjectSearchView {
     }
 }
 
-impl Default for ProjectSearchBar {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
 impl ProjectSearchBar {
     pub fn new() -> Self {
         Self {
-            active_project_search: Default::default(),
-            subscription: Default::default(),
+            active_project_search: None,
+            subscription: None,
         }
     }
 
@@ -1303,37 +1383,6 @@ impl ProjectSearchBar {
         }
     }
 
-    fn search_in_new(workspace: &mut Workspace, _: &SearchInNew, cx: &mut ViewContext<Workspace>) {
-        if let Some(search_view) = workspace
-            .active_item(cx)
-            .and_then(|item| item.downcast::<ProjectSearchView>())
-        {
-            let new_query = search_view.update(cx, |search_view, cx| {
-                let new_query = search_view.build_search_query(cx);
-                if new_query.is_some() {
-                    if let Some(old_query) = search_view.model.read(cx).active_query.clone() {
-                        search_view.query_editor.update(cx, |editor, cx| {
-                            editor.set_text(old_query.as_str(), cx);
-                        });
-                        search_view.search_options = SearchOptions::from_query(&old_query);
-                    }
-                }
-                new_query
-            });
-            if let Some(new_query) = new_query {
-                let model = cx.new_model(|cx| {
-                    let mut model = ProjectSearch::new(workspace.project().clone(), cx);
-                    model.search(new_query, cx);
-                    model
-                });
-                workspace.add_item(
-                    Box::new(cx.new_view(|cx| ProjectSearchView::new(model, cx, None))),
-                    cx,
-                );
-            }
-        }
-    }
-
     fn tab(&mut self, _: &editor::Tab, cx: &mut ViewContext<Self>) {
         self.cycle_field(Direction::Next, cx);
     }
@@ -1502,6 +1551,22 @@ impl ProjectSearchBar {
         }
     }
 
+    pub fn select_next_match(&mut self, _: &SelectNextMatch, cx: &mut ViewContext<Self>) {
+        if let Some(search) = self.active_project_search.as_ref() {
+            search.update(cx, |this, cx| {
+                this.select_match(Direction::Next, cx);
+            })
+        }
+    }
+
+    fn select_prev_match(&mut self, _: &SelectPrevMatch, cx: &mut ViewContext<Self>) {
+        if let Some(search) = self.active_project_search.as_ref() {
+            search.update(cx, |this, cx| {
+                this.select_match(Direction::Prev, cx);
+            })
+        }
+    }
+
     fn new_placeholder_text(&self, cx: &mut ViewContext<Self>) -> Option<String> {
         let previous_query_keystrokes = cx
             .bindings_for_action(&PreviousHistoryQuery {})
@@ -1870,6 +1935,8 @@ impl Render for ProjectSearchBar {
                     }))
                 })
             })
+            .on_action(cx.listener(Self::select_next_match))
+            .on_action(cx.listener(Self::select_prev_match))
             .child(
                 h_flex()
                     .justify_between()
@@ -1963,6 +2030,60 @@ impl ToolbarItemView for ProjectSearchBar {
     }
 }
 
+fn register_workspace_action<A: Action>(
+    workspace: &mut Workspace,
+    callback: fn(&mut ProjectSearchBar, &A, &mut ViewContext<ProjectSearchBar>),
+) {
+    workspace.register_action(move |workspace, action: &A, cx| {
+        if workspace.has_active_modal(cx) {
+            cx.propagate();
+            return;
+        }
+
+        workspace.active_pane().update(cx, |pane, cx| {
+            pane.toolbar().update(cx, move |workspace, cx| {
+                if let Some(search_bar) = workspace.item_of_type::<ProjectSearchBar>() {
+                    search_bar.update(cx, move |search_bar, cx| {
+                        if search_bar.active_project_search.is_some() {
+                            callback(search_bar, action, cx);
+                            cx.notify();
+                        } else {
+                            cx.propagate();
+                        }
+                    });
+                }
+            });
+        })
+    });
+}
+
+fn register_workspace_action_for_dismissed_search<A: Action>(
+    workspace: &mut Workspace,
+    callback: fn(&mut Workspace, &A, &mut ViewContext<Workspace>),
+) {
+    workspace.register_action(move |workspace, action: &A, cx| {
+        if workspace.has_active_modal(cx) {
+            cx.propagate();
+            return;
+        }
+
+        let should_notify = workspace
+            .active_pane()
+            .read(cx)
+            .toolbar()
+            .read(cx)
+            .item_of_type::<ProjectSearchBar>()
+            .map(|search_bar| search_bar.read(cx).active_project_search.is_none())
+            .unwrap_or(false);
+        if should_notify {
+            callback(workspace, action, cx);
+            cx.notify();
+        } else {
+            cx.propagate();
+        }
+    });
+}
+
 #[cfg(test)]
 pub mod tests {
     use super::*;

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -387,7 +387,7 @@ impl Render for TerminalPanel {
             },
             cx,
         );
-        BufferSearchBar::register_inner(&mut registrar);
+        BufferSearchBar::register(&mut registrar);
         registrar.into_div().size_full().child(self.pane.clone())
     }
 }