Add query history and replace buttons

Piotr Osiewicz created

Change summary

crates/search2/src/buffer_search.rs | 215 +++++++++++++++---------------
crates/search2/src/search.rs        |   5 
crates/search2/src/search_bar.rs    |  17 -
3 files changed, 113 insertions(+), 124 deletions(-)

Detailed changes

crates/search2/src/buffer_search.rs 🔗

@@ -11,8 +11,8 @@ use editor::Editor;
 use futures::channel::oneshot;
 use gpui::{
     action, actions, div, red, Action, AppContext, Component, Div, EventEmitter,
-    ParentComponent as _, Render, Styled, Subscription, Task, View, ViewContext,
-    VisualContext as _, WindowContext,
+    InteractiveComponent, ParentComponent as _, Render, Styled, Subscription, Task, View,
+    ViewContext, VisualContext as _, WindowContext,
 };
 use project::search::SearchQuery;
 use std::{any::Any, sync::Arc};
@@ -38,7 +38,6 @@ pub enum Event {
 }
 
 pub fn init(cx: &mut AppContext) {
-    dbg!("Registered");
     cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace))
         .detach();
 }
@@ -78,45 +77,51 @@ impl Render for BufferSearchBar {
             .map(|active_searchable_item| active_searchable_item.supported_options())
             .unwrap_or_default();
 
-        // let previous_query_keystrokes =
-        //     cx.binding_for_action(&PreviousHistoryQuery {})
-        //         .map(|binding| {
-        //             binding
-        //                 .keystrokes()
-        //                 .iter()
-        //                 .map(|k| k.to_string())
-        //                 .collect::<Vec<_>>()
-        //         });
-        // let next_query_keystrokes = cx.binding_for_action(&NextHistoryQuery {}).map(|binding| {
-        //     binding
-        //         .keystrokes()
-        //         .iter()
-        //         .map(|k| k.to_string())
-        //         .collect::<Vec<_>>()
-        // });
-        // let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
-        //     (Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
-        //         format!(
-        //             "Search ({}/{} for previous/next query)",
-        //             previous_query_keystrokes.join(" "),
-        //             next_query_keystrokes.join(" ")
-        //         )
-        //     }
-        //     (None, Some(next_query_keystrokes)) => {
-        //         format!(
-        //             "Search ({} for next query)",
-        //             next_query_keystrokes.join(" ")
-        //         )
-        //     }
-        //     (Some(previous_query_keystrokes), None) => {
-        //         format!(
-        //             "Search ({} for previous query)",
-        //             previous_query_keystrokes.join(" ")
-        //         )
-        //     }
-        //     (None, None) => String::new(),
-        // };
-        let new_placeholder_text = Arc::from("Search for..");
+        let previous_query_keystrokes = cx
+            .bindings_for_action(&PreviousHistoryQuery {})
+            .into_iter()
+            .next()
+            .map(|binding| {
+                binding
+                    .keystrokes()
+                    .iter()
+                    .map(|k| k.to_string())
+                    .collect::<Vec<_>>()
+            });
+        let next_query_keystrokes = cx
+            .bindings_for_action(&NextHistoryQuery {})
+            .into_iter()
+            .next()
+            .map(|binding| {
+                binding
+                    .keystrokes()
+                    .iter()
+                    .map(|k| k.to_string())
+                    .collect::<Vec<_>>()
+            });
+        let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
+            (Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
+                format!(
+                    "Search ({}/{} for previous/next query)",
+                    previous_query_keystrokes.join(" "),
+                    next_query_keystrokes.join(" ")
+                )
+            }
+            (None, Some(next_query_keystrokes)) => {
+                format!(
+                    "Search ({} for next query)",
+                    next_query_keystrokes.join(" ")
+                )
+            }
+            (Some(previous_query_keystrokes), None) => {
+                format!(
+                    "Search ({} for previous query)",
+                    previous_query_keystrokes.join(" ")
+                )
+            }
+            (None, None) => String::new(),
+        };
+        let new_placeholder_text = Arc::from(new_placeholder_text);
         self.query_editor.update(cx, |editor, cx| {
             editor.set_placeholder_text(new_placeholder_text, cx);
         });
@@ -159,9 +164,9 @@ impl Render for BufferSearchBar {
 
                 Some(ui::Label::new(message))
             });
-        let nav_button_for_direction = |label, direction, cx: &mut ViewContext<Self>| {
+        let nav_button_for_direction = |icon, direction, cx: &mut ViewContext<Self>| {
             render_nav_button(
-                label,
+                icon,
                 direction,
                 self.active_match_index.is_some(),
                 move |this, cx| match direction {
@@ -172,12 +177,32 @@ impl Render for BufferSearchBar {
             )
         };
         let should_show_replace_input = self.replace_enabled && supported_options.replacement;
-        let replace_all = should_show_replace_input.then(|| {
-            super::replace_action::<Self>(ReplaceAll, "Replace all", ui::Icon::ReplaceAll)
-        });
+        let replace_all = should_show_replace_input
+            .then(|| super::render_replace_button::<Self>(ReplaceAll, ui::Icon::ReplaceAll));
         let replace_next = should_show_replace_input
-            .then(|| super::replace_action::<Self>(ReplaceNext, "Replace next", ui::Icon::Replace));
+            .then(|| super::render_replace_button::<Self>(ReplaceNext, ui::Icon::Replace));
+        let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx);
+
         h_stack()
+            .key_context("BufferSearchBar")
+            .when(in_replace, |this| {
+                this.key_context("in_replace")
+                    .on_action(Self::replace_next)
+                    .on_action(Self::replace_all)
+            })
+            .on_action(Self::previous_history_query)
+            .on_action(Self::next_history_query)
+            .when(supported_options.case, |this| {
+                this.on_action(Self::toggle_case_sensitive)
+            })
+            .when(supported_options.word, |this| {
+                this.on_action(Self::toggle_whole_word)
+            })
+            .when(supported_options.replacement, |this| {
+                this.on_action(Self::toggle_replace)
+            })
+            .on_action(Self::select_next_match)
+            .on_action(Self::select_prev_match)
             .w_full()
             .p_1()
             .child(
@@ -226,9 +251,18 @@ impl Render for BufferSearchBar {
                 h_stack()
                     .gap_0p5()
                     .flex_none()
+                    .child(self.render_action_button(cx))
                     .children(match_count)
-                    .child(nav_button_for_direction("<", Direction::Prev, cx))
-                    .child(nav_button_for_direction(">", Direction::Next, cx)),
+                    .child(nav_button_for_direction(
+                        ui::Icon::ChevronLeft,
+                        Direction::Prev,
+                        cx,
+                    ))
+                    .child(nav_button_for_direction(
+                        ui::Icon::ChevronRight,
+                        Direction::Next,
+                        cx,
+                    )),
             )
 
         // let query_column = Flex::row()
@@ -343,7 +377,7 @@ impl ToolbarItemView for BufferSearchBar {
         cx.notify();
         self.active_searchable_item_subscription.take();
         self.active_searchable_item.take();
-        dbg!("Take?");
+
         self.pending_search.take();
 
         if let Some(searchable_item_handle) =
@@ -382,7 +416,8 @@ impl BufferSearchBar {
         workspace.register_action(|workspace, a: &Deploy, cx| {
             workspace.active_pane().update(cx, |this, cx| {
                 this.toolbar().update(cx, |this, cx| {
-                    if this.item_of_type::<BufferSearchBar>().is_some() {
+                    if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
+                        search_bar.update(cx, |this, cx| this.dismiss(&Dismiss, cx));
                         return;
                     }
                     let view = cx.build_view(|cx| BufferSearchBar::new(cx));
@@ -392,46 +427,8 @@ impl BufferSearchBar {
                 })
             });
         });
-        fn register_action<A: Action>(
-            workspace: &mut Workspace,
-            update: fn(&mut BufferSearchBar, &mut ViewContext<'_, BufferSearchBar>),
-        ) {
-            workspace.register_action(move |workspace, _: &A, cx| {
-                workspace.active_pane().update(cx, move |this, cx| {
-                    this.toolbar().update(cx, move |toolbar, cx| {
-                        let Some(search_bar) = toolbar.item_of_type::<BufferSearchBar>() else {
-                            return;
-                        };
-                        search_bar.update(cx, |this, cx| update(this, cx))
-                    })
-                });
-            });
-        }
-        register_action::<ToggleCaseSensitive>(workspace, |this, cx| {
-            this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx)
-        });
-        register_action::<ToggleWholeWord>(workspace, |this: &mut BufferSearchBar, cx| {
-            this.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
-        });
-        register_action::<ToggleReplace>(workspace, |this: &mut BufferSearchBar, cx| {
-            dbg!("Toggling");
-            this.toggle_replace(&ToggleReplace, cx)
-        });
-        // workspace.register_action(|workspace, _: &ToggleCaseSensitive, cx| {
-        //     workspace.active_pane().update(cx, |this, cx| {
-        //         this.toolbar().update(cx, |toolbar, cx| {
-        //             let Some(search_bar) = toolbar.item_of_type::<BufferSearchBar>() else {
-        //                 return;
-        //             };
-        //             search_bar.update(cx, |this, cx| {
-        //                 this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
-        //             })
-        //         })
-        //     });
-        // });
     }
     pub fn new(cx: &mut ViewContext<Self>) -> Self {
-        dbg!("New");
         let query_editor = cx.build_view(|cx| Editor::single_line(cx));
         cx.subscribe(&query_editor, Self::on_query_editor_event)
             .detach();
@@ -463,7 +460,7 @@ impl BufferSearchBar {
 
     pub fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
         self.dismissed = true;
-        dbg!("Dismissed :(");
+
         for searchable_item in self.searchable_items_with_matches.keys() {
             if let Some(searchable_item) =
                 WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx)
@@ -495,10 +492,9 @@ impl BufferSearchBar {
 
     pub fn show(&mut self, cx: &mut ViewContext<Self>) -> bool {
         if self.active_searchable_item.is_none() {
-            dbg!("Hey");
             return false;
         }
-        dbg!("not dismissed");
+
         self.dismissed = false;
         cx.notify();
         cx.emit(Event::UpdateLocation);
@@ -590,13 +586,7 @@ impl BufferSearchBar {
         self.update_matches(cx)
     }
 
-    fn render_action_button(
-        &self,
-        icon: &'static str,
-        cx: &mut ViewContext<Self>,
-    ) -> impl Component<Self> {
-        let tooltip = "Select All Matches";
-        let theme = cx.theme();
+    fn render_action_button(&self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
         // let tooltip_style = theme.tooltip.clone();
 
         // let style = theme.search.action_button.clone();
@@ -695,8 +685,13 @@ impl BufferSearchBar {
                     .searchable_items_with_matches
                     .get(&searchable_item.downgrade())
                 {
-                    let new_match_index = searchable_item
-                        .match_index_for_direction(matches, index, direction, count, cx);
+                    let new_match_index = searchable_item.match_index_for_direction(
+                        matches,
+                        index,
+                        direction,
+                        dbg!(count),
+                        cx,
+                    );
                     searchable_item.update_matches(matches, cx);
                     searchable_item.activate_match(new_match_index, matches, cx);
                 }
@@ -769,6 +764,7 @@ impl BufferSearchBar {
     }
 
     fn on_active_searchable_item_event(&mut self, event: &SearchEvent, cx: &mut ViewContext<Self>) {
+        dbg!(&event);
         match event {
             SearchEvent::MatchesInvalidated => {
                 let _ = self.update_matches(cx);
@@ -777,6 +773,12 @@ impl BufferSearchBar {
         }
     }
 
+    fn toggle_case_sensitive(&mut self, _: &ToggleCaseSensitive, cx: &mut ViewContext<Self>) {
+        self.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx)
+    }
+    fn toggle_whole_word(&mut self, _: &ToggleWholeWord, cx: &mut ViewContext<Self>) {
+        self.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
+    }
     fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
         let mut active_item_matches = None;
         for (searchable_item, matches) in self.searchable_items_with_matches.drain() {
@@ -799,7 +801,7 @@ impl BufferSearchBar {
         let (done_tx, done_rx) = oneshot::channel();
         let query = self.query(cx);
         self.pending_search.take();
-        dbg!("update_matches");
+
         if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
             if query.is_empty() {
                 self.active_match_index.take();
@@ -841,26 +843,23 @@ impl BufferSearchBar {
                 .into();
                 self.active_search = Some(query.clone());
                 let query_text = query.as_str().to_string();
-                dbg!(&query_text);
+
                 let matches = active_searchable_item.find_matches(query, cx);
 
                 let active_searchable_item = active_searchable_item.downgrade();
                 self.pending_search = Some(cx.spawn(|this, mut cx| async move {
                     let matches = matches.await;
-                    //dbg!(&matches);
+
                     this.update(&mut cx, |this, cx| {
-                        dbg!("Updating!!");
                         if let Some(active_searchable_item) =
                             WeakSearchableItemHandle::upgrade(active_searchable_item.as_ref(), cx)
                         {
-                            dbg!("in if!!");
                             this.searchable_items_with_matches
                                 .insert(active_searchable_item.downgrade(), matches);
 
                             this.update_match_index(cx);
                             this.search_history.add(query_text);
                             if !this.dismissed {
-                                dbg!("Not dismissed");
                                 let matches = this
                                     .searchable_items_with_matches
                                     .get(&active_searchable_item.downgrade())

crates/search2/src/search.rs 🔗

@@ -1,6 +1,6 @@
 use bitflags::bitflags;
 pub use buffer_search::BufferSearchBar;
-use gpui::{actions, Action, AnyElement, AppContext, Component, Element, Svg, View};
+use gpui::{actions, Action, AppContext, Component};
 pub use mode::SearchMode;
 use project::search::SearchQuery;
 use ui::ButtonVariant;
@@ -106,9 +106,8 @@ fn toggle_replace_button<V: 'static>(active: bool) -> impl Component<V> {
         .when(active, |button| button.variant(ButtonVariant::Filled))
 }
 
-fn replace_action<V: 'static>(
+fn render_replace_button<V: 'static>(
     action: impl Action + 'static + Send + Sync,
-    name: &'static str,
     icon: ui::Icon,
 ) -> impl Component<V> {
     // todo: add tooltip

crates/search2/src/search_bar.rs 🔗

@@ -1,28 +1,19 @@
 use std::{borrow::Cow, sync::Arc};
 
 use gpui::{div, Action, Component, ViewContext};
-use ui::{Button, ButtonVariant};
+use ui::{Button, ButtonVariant, IconButton};
 use workspace::searchable::Direction;
 
 use crate::mode::{SearchMode, Side};
 
 pub(super) fn render_nav_button<V: 'static>(
-    icon: &'static str,
+    icon: ui::Icon,
     direction: Direction,
+
     active: bool,
     on_click: impl Fn(&mut V, &mut ViewContext<V>) + 'static + Send + Sync,
     cx: &mut ViewContext<V>,
 ) -> impl Component<V> {
-    let tooltip;
-
-    match direction {
-        Direction::Prev => {
-            tooltip = "Select Previous Match";
-        }
-        Direction::Next => {
-            tooltip = "Select Next Match";
-        }
-    };
     // let tooltip_style = cx.theme().tooltip.clone();
     // let cursor_style = if active {
     //     CursorStyle::PointingHand
@@ -30,7 +21,7 @@ pub(super) fn render_nav_button<V: 'static>(
     //     CursorStyle::default()
     // };
     // enum NavButton {}
-    Button::new(icon).on_click(Arc::new(on_click))
+    IconButton::new("search-nav-button", icon).on_click(on_click)
     // MouseEventHandler::new::<NavButton, _>(direction as usize, cx, |state, cx| {
     //     let theme = cx.theme();
     //     let style = theme