buffer_search: Add tooltips, polish keybinds

Piotr Osiewicz created

Change summary

crates/search2/src/buffer_search.rs | 62 +++++++++++++++---------------
crates/search2/src/mode.rs          | 14 +++++++
crates/search2/src/search.rs        | 26 +++++++++---
crates/search2/src/search_bar.rs    | 42 ++++++++++++---------
crates/ui2/src/components/icon.rs   |  2 +
5 files changed, 90 insertions(+), 56 deletions(-)

Detailed changes

crates/search2/src/buffer_search.rs 🔗

@@ -11,14 +11,14 @@ use editor::{Editor, EditorMode};
 use futures::channel::oneshot;
 use gpui::{
     actions, div, red, Action, AppContext, Div, EventEmitter, FocusableView,
-    InteractiveElement as _, IntoElement, ParentElement as _, Render, Styled, Subscription, Task,
-    View, ViewContext, VisualContext as _, WeakView, WindowContext,
+    InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, Styled,
+    Subscription, Task, View, ViewContext, VisualContext as _, WeakView, WindowContext,
 };
 use project::search::SearchQuery;
 use serde::Deserialize;
 use std::{any::Any, sync::Arc};
 
-use ui::{h_stack, Clickable, Icon, IconButton, IconElement};
+use ui::{h_stack, ButtonCommon, Clickable, Icon, IconButton, IconElement, Tooltip};
 use util::ResultExt;
 use workspace::{
     item::ItemHandle,
@@ -131,13 +131,7 @@ impl Render for BufferSearchBar {
         let search_button_for_mode = |mode| {
             let is_active = self.current_mode == mode;
 
-            render_search_mode_button(
-                mode,
-                is_active,
-                cx.listener(move |this, _, cx| {
-                    this.activate_search_mode(mode, cx);
-                }),
-            )
+            render_search_mode_button(mode, is_active)
         };
         let search_option_button = |option| {
             let is_active = self.search_options.contains(option);
@@ -163,23 +157,35 @@ impl Render for BufferSearchBar {
             });
         let should_show_replace_input = self.replace_enabled && supported_options.replacement;
         let replace_all = should_show_replace_input
-            .then(|| super::render_replace_button(ReplaceAll, ui::Icon::ReplaceAll));
-        let replace_next = should_show_replace_input
-            .then(|| super::render_replace_button(ReplaceNext, ui::Icon::Replace));
+            .then(|| super::render_replace_button(ReplaceAll, ui::Icon::ReplaceAll, "Replace all"));
+        let replace_next = should_show_replace_input.then(|| {
+            super::render_replace_button(ReplaceNext, ui::Icon::ReplaceNext, "Replace next")
+        });
         let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx);
 
+        let mut key_context = KeyContext::default();
+        key_context.add("BufferSearchBar");
+        if in_replace {
+            key_context.add("in_replace");
+        }
+
         h_stack()
-            .key_context("BufferSearchBar")
+            .key_context(key_context)
             .on_action(cx.listener(Self::previous_history_query))
             .on_action(cx.listener(Self::next_history_query))
             .on_action(cx.listener(Self::dismiss))
             .on_action(cx.listener(Self::select_next_match))
             .on_action(cx.listener(Self::select_prev_match))
+            .on_action(cx.listener(|this, _: &ActivateRegexMode, cx| {
+                this.activate_search_mode(SearchMode::Regex, cx);
+            }))
+            .on_action(cx.listener(|this, _: &ActivateTextMode, cx| {
+                this.activate_search_mode(SearchMode::Text, cx);
+            }))
             .when(self.supported_options().replacement, |this| {
                 this.on_action(cx.listener(Self::toggle_replace))
                     .when(in_replace, |this| {
-                        this.key_context("in_replace")
-                            .on_action(cx.listener(Self::replace_next))
+                        this.on_action(cx.listener(Self::replace_next))
                             .on_action(cx.listener(Self::replace_all))
                     })
             })
@@ -238,21 +244,19 @@ impl Render for BufferSearchBar {
                 h_stack()
                     .gap_0p5()
                     .flex_none()
-                    .child(self.render_action_button(cx))
+                    .child(self.render_action_button())
                     .children(match_count)
                     .child(render_nav_button(
                         ui::Icon::ChevronLeft,
                         self.active_match_index.is_some(),
-                        cx.listener(move |this, _, cx| {
-                            this.select_prev_match(&Default::default(), cx);
-                        }),
+                        "Select previous match",
+                        &SelectPrevMatch,
                     ))
                     .child(render_nav_button(
                         ui::Icon::ChevronRight,
                         self.active_match_index.is_some(),
-                        cx.listener(move |this, _, cx| {
-                            this.select_next_match(&Default::default(), cx);
-                        }),
+                        "Select next match",
+                        &SelectNextMatch,
                     )),
             )
     }
@@ -597,14 +601,10 @@ impl BufferSearchBar {
         self.update_matches(cx)
     }
 
-    fn render_action_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        // let tooltip_style = theme.tooltip.clone();
-
-        // let style = theme.search.action_button.clone();
-
-        IconButton::new("select-all", ui::Icon::SelectAll).on_click(cx.listener(|this, _, cx| {
-            this.select_all_matches(&SelectAllMatches, cx);
-        }))
+    fn render_action_button(&self) -> impl IntoElement {
+        IconButton::new("select-all", ui::Icon::SelectAll)
+            .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))
+            .tooltip(|cx| Tooltip::for_action("Select all matches", &SelectAllMatches, cx))
     }
 
     pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {

crates/search2/src/mode.rs 🔗

@@ -1,3 +1,7 @@
+use gpui::{Action, SharedString};
+
+use crate::{ActivateRegexMode, ActivateSemanticMode, ActivateTextMode};
+
 // TODO: Update the default search mode to get from config
 #[derive(Copy, Clone, Debug, Default, PartialEq)]
 pub enum SearchMode {
@@ -15,6 +19,16 @@ impl SearchMode {
             SearchMode::Regex => "Regex",
         }
     }
+    pub(crate) fn tooltip(&self) -> SharedString {
+        format!("Activate {} Mode", self.label()).into()
+    }
+    pub(crate) fn action(&self) -> Box<dyn Action> {
+        match self {
+            SearchMode::Text => ActivateTextMode.boxed_clone(),
+            SearchMode::Semantic => ActivateSemanticMode.boxed_clone(),
+            SearchMode::Regex => ActivateRegexMode.boxed_clone(),
+        }
+    }
 }
 
 pub(crate) fn next_mode(mode: &SearchMode, semantic_enabled: bool) -> SearchMode {

crates/search2/src/search.rs 🔗

@@ -3,7 +3,7 @@ pub use buffer_search::BufferSearchBar;
 use gpui::{actions, Action, AppContext, IntoElement};
 pub use mode::SearchMode;
 use project::search::SearchQuery;
-use ui::prelude::*;
+use ui::{prelude::*, Tooltip};
 use ui::{ButtonStyle, Icon, IconButton};
 //pub use project_search::{ProjectSearchBar, ProjectSearchView};
 // use theme::components::{
@@ -84,7 +84,7 @@ impl SearchOptions {
     }
 
     pub fn as_button(&self, active: bool) -> impl IntoElement {
-        IconButton::new(0, self.icon())
+        IconButton::new(self.label(), self.icon())
             .on_click({
                 let action = self.to_toggle_action();
                 move |_, cx| {
@@ -93,26 +93,38 @@ impl SearchOptions {
             })
             .style(ButtonStyle::Subtle)
             .when(active, |button| button.style(ButtonStyle::Filled))
+            .tooltip({
+                let action = self.to_toggle_action();
+                let label: SharedString = format!("Toggle {}", self.label()).into();
+                move |cx| Tooltip::for_action(label.clone(), &*action, cx)
+            })
     }
 }
 
 fn toggle_replace_button(active: bool) -> impl IntoElement {
     // todo: add toggle_replace button
-    IconButton::new(0, Icon::Replace)
+    IconButton::new("buffer-search-bar-toggle-replace-button", Icon::Replace)
         .on_click(|_, cx| {
             cx.dispatch_action(Box::new(ToggleReplace));
             cx.notify();
         })
         .style(ButtonStyle::Subtle)
         .when(active, |button| button.style(ButtonStyle::Filled))
+        .tooltip(|cx| Tooltip::for_action("Toggle replace", &ToggleReplace, cx))
 }
 
 fn render_replace_button(
     action: impl Action + 'static + Send + Sync,
     icon: Icon,
+    tooltip: &'static str,
 ) -> impl IntoElement {
-    // todo: add tooltip
-    IconButton::new(0, icon).on_click(move |_, cx| {
-        cx.dispatch_action(action.boxed_clone());
-    })
+    let id: SharedString = format!("search-replace-{}", action.name()).into();
+    IconButton::new(id, icon)
+        .tooltip({
+            let action = action.boxed_clone();
+            move |cx| Tooltip::for_action(tooltip, &*action, cx)
+        })
+        .on_click(move |_, cx| {
+            cx.dispatch_action(action.boxed_clone());
+        })
 }

crates/search2/src/search_bar.rs 🔗

@@ -1,30 +1,36 @@
-use gpui::{ClickEvent, IntoElement, WindowContext};
-use ui::prelude::*;
+use gpui::{Action, IntoElement};
+use ui::{prelude::*, Tooltip};
 use ui::{Button, IconButton};
 
 use crate::mode::SearchMode;
 
 pub(super) fn render_nav_button(
     icon: ui::Icon,
-    _active: bool,
-    on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
+    active: bool,
+    tooltip: &'static str,
+    action: &'static dyn Action,
 ) -> impl IntoElement {
-    // let tooltip_style = cx.theme().tooltip.clone();
-    // let cursor_style = if active {
-    //     CursorStyle::PointingHand
-    // } else {
-    //     CursorStyle::default()
-    // };
-    // enum NavButton {}
-    IconButton::new("search-nav-button", icon).on_click(on_click)
+    IconButton::new(
+        SharedString::from(format!("search-nav-button-{}", action.name())),
+        icon,
+    )
+    .on_click(|_, cx| cx.dispatch_action(action.boxed_clone()))
+    .tooltip(move |cx| Tooltip::for_action(tooltip, action, cx))
+    .disabled(!active)
 }
 
-pub(crate) fn render_search_mode_button(
-    mode: SearchMode,
-    is_active: bool,
-    on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
-) -> Button {
+pub(crate) fn render_search_mode_button(mode: SearchMode, is_active: bool) -> Button {
     Button::new(mode.label(), mode.label())
         .selected(is_active)
-        .on_click(on_click)
+        .on_click({
+            let action = mode.action();
+            move |_, cx| {
+                cx.dispatch_action(action.boxed_clone());
+            }
+        })
+        .tooltip({
+            let action = mode.action();
+            let tooltip_text = mode.tooltip();
+            move |cx| Tooltip::for_action(tooltip_text.clone(), &*action, cx)
+        })
 }

crates/ui2/src/components/icon.rs 🔗

@@ -80,6 +80,7 @@ pub enum Icon {
     Quote,
     Replace,
     ReplaceAll,
+    ReplaceNext,
     Screen,
     SelectAll,
     Split,
@@ -157,6 +158,7 @@ impl Icon {
             Icon::Quote => "icons/quote.svg",
             Icon::Replace => "icons/replace.svg",
             Icon::ReplaceAll => "icons/replace_all.svg",
+            Icon::ReplaceNext => "icons/replace_next.svg",
             Icon::Screen => "icons/desktop.svg",
             Icon::SelectAll => "icons/select-all.svg",
             Icon::Split => "icons/split.svg",