Re-enable buffer search in assistant

Antonio Scandurra created

Change summary

assets/keymaps/default.json        |  7 ++
crates/ai/src/assistant.rs         | 92 ++++++++++++++++++++++++++++---
crates/search/src/buffer_search.rs | 10 ++-
crates/workspace/src/pane.rs       | 13 ++--
crates/workspace/src/toolbar.rs    | 74 +++++++++++--------------
5 files changed, 134 insertions(+), 62 deletions(-)

Detailed changes

assets/keymaps/default.json 🔗

@@ -197,6 +197,13 @@
       "cmd-alt-enter": "editor::NewlineBelow"
     }
   },
+  {
+    "context": "AssistantPanel",
+    "bindings": {
+        "cmd-g": "search::SelectNextMatch",
+        "cmd-shift-g": "search::SelectPrevMatch"
+    }
+  },
   {
     "context": "ConversationEditor > Editor",
     "bindings": {

crates/ai/src/assistant.rs 🔗

@@ -24,6 +24,7 @@ use gpui::{
 };
 use isahc::{http::StatusCode, Request, RequestExt};
 use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _};
+use search::BufferSearchBar;
 use serde::Deserialize;
 use settings::SettingsStore;
 use std::{
@@ -46,7 +47,8 @@ use util::{
 use workspace::{
     dock::{DockPosition, Panel},
     item::Item,
-    Save, ToggleZoom, Workspace,
+    searchable::Direction,
+    Save, ToggleZoom, Toolbar, Workspace,
 };
 
 const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";
@@ -93,6 +95,10 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(AssistantPanel::save_api_key);
     cx.add_action(AssistantPanel::reset_api_key);
     cx.add_action(AssistantPanel::toggle_zoom);
+    cx.add_action(AssistantPanel::deploy);
+    cx.add_action(AssistantPanel::select_next_match);
+    cx.add_action(AssistantPanel::select_prev_match);
+    cx.add_action(AssistantPanel::handle_editor_cancel);
     cx.add_action(
         |workspace: &mut Workspace, _: &ToggleFocus, cx: &mut ViewContext<Workspace>| {
             workspace.toggle_panel_focus::<AssistantPanel>(cx);
@@ -118,6 +124,7 @@ pub struct AssistantPanel {
     saved_conversations_list_state: UniformListState,
     zoomed: bool,
     has_focus: bool,
+    toolbar: ViewHandle<Toolbar>,
     api_key: Rc<RefCell<Option<String>>>,
     api_key_editor: Option<ViewHandle<Editor>>,
     has_read_credentials: bool,
@@ -161,6 +168,12 @@ impl AssistantPanel {
                         anyhow::Ok(())
                     });
 
+                    let toolbar = cx.add_view(|cx| {
+                        let mut toolbar = Toolbar::new(None);
+                        toolbar.set_can_navigate(false, cx);
+                        toolbar.add_item(cx.add_view(|cx| BufferSearchBar::new(cx)), cx);
+                        toolbar
+                    });
                     let mut this = Self {
                         active_editor_index: Default::default(),
                         editors: Default::default(),
@@ -168,6 +181,7 @@ impl AssistantPanel {
                         saved_conversations_list_state: Default::default(),
                         zoomed: false,
                         has_focus: false,
+                        toolbar,
                         api_key: Rc::new(RefCell::new(None)),
                         api_key_editor: None,
                         has_read_credentials: false,
@@ -220,11 +234,27 @@ impl AssistantPanel {
         self.subscriptions
             .push(cx.observe(&conversation, |_, _, cx| cx.notify()));
 
-        self.active_editor_index = Some(self.editors.len());
-        self.editors.push(editor.clone());
-        if self.has_focus(cx) {
-            cx.focus(&editor);
+        let index = self.editors.len();
+        self.editors.push(editor);
+        self.set_active_editor_index(Some(index), cx);
+    }
+
+    fn set_active_editor_index(&mut self, index: Option<usize>, cx: &mut ViewContext<Self>) {
+        self.active_editor_index = index;
+        if let Some(editor) = self.active_editor() {
+            let editor = editor.read(cx).editor.clone();
+            self.toolbar.update(cx, |toolbar, cx| {
+                toolbar.set_active_item(Some(&editor), cx);
+            });
+            if self.has_focus(cx) {
+                cx.focus(&editor);
+            }
+        } else {
+            self.toolbar.update(cx, |toolbar, cx| {
+                toolbar.set_active_item(None, cx);
+            });
         }
+
         cx.notify();
     }
 
@@ -275,6 +305,39 @@ impl AssistantPanel {
         }
     }
 
+    fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext<Self>) {
+        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
+            if search_bar.update(cx, |search_bar, cx| search_bar.show(action.focus, true, cx)) {
+                return;
+            }
+        }
+        cx.propagate_action();
+    }
+
+    fn handle_editor_cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
+        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
+            if !search_bar.read(cx).is_dismissed() {
+                search_bar.update(cx, |search_bar, cx| {
+                    search_bar.dismiss(&Default::default(), cx)
+                });
+                return;
+            }
+        }
+        cx.propagate_action();
+    }
+
+    fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext<Self>) {
+        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
+            search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, cx));
+        }
+    }
+
+    fn select_prev_match(&mut self, _: &search::SelectPrevMatch, cx: &mut ViewContext<Self>) {
+        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
+            search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, cx));
+        }
+    }
+
     fn active_editor(&self) -> Option<&ViewHandle<ConversationEditor>> {
         self.editors.get(self.active_editor_index?)
     }
@@ -287,8 +350,7 @@ impl AssistantPanel {
             .mouse::<ListConversations>(0)
             .with_cursor_style(CursorStyle::PointingHand)
             .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
-                this.active_editor_index = None;
-                cx.notify();
+                this.set_active_editor_index(None, cx);
             })
     }
 
@@ -425,8 +487,7 @@ impl AssistantPanel {
 
     fn open_conversation(&mut self, path: PathBuf, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
         if let Some(ix) = self.editor_index_for_path(&path, cx) {
-            self.active_editor_index = Some(ix);
-            cx.notify();
+            self.set_active_editor_index(Some(ix), cx);
             return Task::ready(Ok(()));
         }
 
@@ -444,7 +505,7 @@ impl AssistantPanel {
                 // If, by the time we've loaded the conversation, the user has already opened
                 // the same conversation, we don't want to open it again.
                 if let Some(ix) = this.editor_index_for_path(&path, cx) {
-                    this.active_editor_index = Some(ix);
+                    this.set_active_editor_index(Some(ix), cx);
                 } else {
                     let editor = cx
                         .add_view(|cx| ConversationEditor::from_conversation(conversation, fs, cx));
@@ -541,6 +602,11 @@ impl View for AssistantPanel {
                         .constrained()
                         .with_height(theme.workspace.tab_bar.height),
                 )
+                .with_children(if self.toolbar.read(cx).hidden() {
+                    None
+                } else {
+                    Some(ChildView::new(&self.toolbar, cx).expanded())
+                })
                 .with_child(if let Some(editor) = self.active_editor() {
                     ChildView::new(editor, cx).flex(1., true).into_any()
                 } else {
@@ -563,6 +629,8 @@ impl View for AssistantPanel {
 
     fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
         self.has_focus = true;
+        self.toolbar
+            .update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
         if cx.is_self_focused() {
             if let Some(editor) = self.active_editor() {
                 cx.focus(editor);
@@ -572,8 +640,10 @@ impl View for AssistantPanel {
         }
     }
 
-    fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
+    fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
         self.has_focus = false;
+        self.toolbar
+            .update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
     }
 }
 

crates/search/src/buffer_search.rs 🔗

@@ -259,7 +259,11 @@ impl BufferSearchBar {
         }
     }
 
-    fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
+    pub fn is_dismissed(&self) -> bool {
+        self.dismissed
+    }
+
+    pub fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
         self.dismissed = true;
         for searchable_item in self.seachable_items_with_matches.keys() {
             if let Some(searchable_item) =
@@ -275,7 +279,7 @@ impl BufferSearchBar {
         cx.notify();
     }
 
-    fn show(&mut self, focus: bool, suggest_query: bool, cx: &mut ViewContext<Self>) -> bool {
+    pub fn show(&mut self, focus: bool, suggest_query: bool, cx: &mut ViewContext<Self>) -> bool {
         let searchable_item = if let Some(searchable_item) = &self.active_searchable_item {
             SearchableItemHandle::boxed_clone(searchable_item.as_ref())
         } else {
@@ -484,7 +488,7 @@ impl BufferSearchBar {
         self.select_match(Direction::Prev, cx);
     }
 
-    fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
+    pub fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
         if let Some(index) = self.active_match_index {
             if let Some(searchable_item) = self.active_searchable_item.as_ref() {
                 if let Some(matches) = self

crates/workspace/src/pane.rs 🔗

@@ -1,9 +1,10 @@
 mod dragged_item_receiver;
 
 use super::{ItemHandle, SplitDirection};
+pub use crate::toolbar::Toolbar;
 use crate::{
-    item::WeakItemHandle, notify_of_new_dock, toolbar::Toolbar, AutosaveSetting, Item,
-    NewCenterTerminal, NewFile, NewSearch, ToggleZoom, Workspace, WorkspaceSettings,
+    item::WeakItemHandle, notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile,
+    NewSearch, ToggleZoom, Workspace, WorkspaceSettings,
 };
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
@@ -250,7 +251,7 @@ impl Pane {
                 pane: handle.clone(),
                 next_timestamp,
             }))),
-            toolbar: cx.add_view(|_| Toolbar::new(handle)),
+            toolbar: cx.add_view(|_| Toolbar::new(Some(handle))),
             tab_bar_context_menu: TabBarContextMenu {
                 kind: TabBarContextMenuKind::New,
                 handle: context_menu,
@@ -1112,7 +1113,7 @@ impl Pane {
             .get(self.active_item_index)
             .map(|item| item.as_ref());
         self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.set_active_pane_item(active_item, cx);
+            toolbar.set_active_item(active_item, cx);
         });
     }
 
@@ -1602,7 +1603,7 @@ impl View for Pane {
         }
 
         self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.pane_focus_update(true, cx);
+            toolbar.focus_changed(true, cx);
         });
 
         if let Some(active_item) = self.active_item() {
@@ -1631,7 +1632,7 @@ impl View for Pane {
     fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         self.has_focus = false;
         self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.pane_focus_update(false, cx);
+            toolbar.focus_changed(false, cx);
         });
         cx.notify();
     }

crates/workspace/src/toolbar.rs 🔗

@@ -38,7 +38,7 @@ trait ToolbarItemViewHandle {
         active_pane_item: Option<&dyn ItemHandle>,
         cx: &mut WindowContext,
     ) -> ToolbarItemLocation;
-    fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext);
+    fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext);
     fn row_count(&self, cx: &WindowContext) -> usize;
 }
 
@@ -51,10 +51,10 @@ pub enum ToolbarItemLocation {
 }
 
 pub struct Toolbar {
-    active_pane_item: Option<Box<dyn ItemHandle>>,
+    active_item: Option<Box<dyn ItemHandle>>,
     hidden: bool,
     can_navigate: bool,
-    pane: WeakViewHandle<Pane>,
+    pane: Option<WeakViewHandle<Pane>>,
     items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
 }
 
@@ -121,7 +121,7 @@ impl View for Toolbar {
         let pane = self.pane.clone();
         let mut enable_go_backward = false;
         let mut enable_go_forward = false;
-        if let Some(pane) = pane.upgrade(cx) {
+        if let Some(pane) = pane.and_then(|pane| pane.upgrade(cx)) {
             let pane = pane.read(cx);
             enable_go_backward = pane.can_navigate_backward();
             enable_go_forward = pane.can_navigate_forward();
@@ -143,19 +143,17 @@ impl View for Toolbar {
                 enable_go_backward,
                 spacing,
                 {
-                    let pane = pane.clone();
                     move |toolbar, cx| {
-                        if let Some(workspace) = toolbar
-                            .pane
-                            .upgrade(cx)
-                            .and_then(|pane| pane.read(cx).workspace().upgrade(cx))
+                        if let Some(pane) = toolbar.pane.as_ref().and_then(|pane| pane.upgrade(cx))
                         {
-                            let pane = pane.clone();
-                            cx.window_context().defer(move |cx| {
-                                workspace.update(cx, |workspace, cx| {
-                                    workspace.go_back(pane.clone(), cx).detach_and_log_err(cx);
-                                });
-                            })
+                            if let Some(workspace) = pane.read(cx).workspace().upgrade(cx) {
+                                let pane = pane.downgrade();
+                                cx.window_context().defer(move |cx| {
+                                    workspace.update(cx, |workspace, cx| {
+                                        workspace.go_back(pane, cx).detach_and_log_err(cx);
+                                    });
+                                })
+                            }
                         }
                     }
                 },
@@ -171,21 +169,17 @@ impl View for Toolbar {
                 enable_go_forward,
                 spacing,
                 {
-                    let pane = pane.clone();
                     move |toolbar, cx| {
-                        if let Some(workspace) = toolbar
-                            .pane
-                            .upgrade(cx)
-                            .and_then(|pane| pane.read(cx).workspace().upgrade(cx))
+                        if let Some(pane) = toolbar.pane.as_ref().and_then(|pane| pane.upgrade(cx))
                         {
-                            let pane = pane.clone();
-                            cx.window_context().defer(move |cx| {
-                                workspace.update(cx, |workspace, cx| {
-                                    workspace
-                                        .go_forward(pane.clone(), cx)
-                                        .detach_and_log_err(cx);
-                                });
-                            });
+                            if let Some(workspace) = pane.read(cx).workspace().upgrade(cx) {
+                                let pane = pane.downgrade();
+                                cx.window_context().defer(move |cx| {
+                                    workspace.update(cx, |workspace, cx| {
+                                        workspace.go_forward(pane, cx).detach_and_log_err(cx);
+                                    });
+                                })
+                            }
                         }
                     }
                 },
@@ -269,9 +263,9 @@ fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>
 }
 
 impl Toolbar {
-    pub fn new(pane: WeakViewHandle<Pane>) -> Self {
+    pub fn new(pane: Option<WeakViewHandle<Pane>>) -> Self {
         Self {
-            active_pane_item: None,
+            active_item: None,
             pane,
             items: Default::default(),
             hidden: false,
@@ -288,7 +282,7 @@ impl Toolbar {
     where
         T: 'static + ToolbarItemView,
     {
-        let location = item.set_active_pane_item(self.active_pane_item.as_deref(), cx);
+        let location = item.set_active_pane_item(self.active_item.as_deref(), cx);
         cx.subscribe(&item, |this, item, event, cx| {
             if let Some((_, current_location)) =
                 this.items.iter_mut().find(|(i, _)| i.id() == item.id())
@@ -307,20 +301,16 @@ impl Toolbar {
         cx.notify();
     }
 
-    pub fn set_active_pane_item(
-        &mut self,
-        pane_item: Option<&dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.active_pane_item = pane_item.map(|item| item.boxed_clone());
+    pub fn set_active_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
+        self.active_item = item.map(|item| item.boxed_clone());
         self.hidden = self
-            .active_pane_item
+            .active_item
             .as_ref()
             .map(|item| !item.show_toolbar(cx))
             .unwrap_or(false);
 
         for (toolbar_item, current_location) in self.items.iter_mut() {
-            let new_location = toolbar_item.set_active_pane_item(pane_item, cx);
+            let new_location = toolbar_item.set_active_pane_item(item, cx);
             if new_location != *current_location {
                 *current_location = new_location;
                 cx.notify();
@@ -328,9 +318,9 @@ impl Toolbar {
         }
     }
 
-    pub fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut ViewContext<Self>) {
+    pub fn focus_changed(&mut self, focused: bool, cx: &mut ViewContext<Self>) {
         for (toolbar_item, _) in self.items.iter_mut() {
-            toolbar_item.pane_focus_update(pane_focused, cx);
+            toolbar_item.focus_changed(focused, cx);
         }
     }
 
@@ -364,7 +354,7 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
         })
     }
 
-    fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext) {
+    fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext) {
         self.update(cx, |this, cx| {
             this.pane_focus_update(pane_focused, cx);
             cx.notify();