Inform toolbars when active item changes

Max Brunsfeld and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

Cargo.lock                   |  1 
crates/find/Cargo.toml       |  1 
crates/find/src/find.rs      | 71 ++++++++++++++++++++++++---
crates/workspace/src/pane.rs | 98 +++++++++++++++++++++++++++++++------
4 files changed, 144 insertions(+), 27 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1725,6 +1725,7 @@ version = "0.1.0"
 dependencies = [
  "editor",
  "gpui",
+ "postage",
  "workspace",
 ]
 

crates/find/Cargo.toml 🔗

@@ -10,3 +10,4 @@ path = "src/find.rs"
 editor = { path = "../editor" }
 gpui = { path = "../gpui" }
 workspace = { path = "../workspace" }
+postage = { version = "0.4.1", features = ["futures-traits"] }

crates/find/src/find.rs 🔗

@@ -1,8 +1,11 @@
+use editor::{Editor, EditorSettings};
 use gpui::{
-    action, color::Color, elements::*, keymap::Binding, Entity, MutableAppContext, RenderContext,
-    View, ViewContext,
+    action, elements::*, keymap::Binding, Entity, MutableAppContext, RenderContext, View,
+    ViewContext, ViewHandle,
 };
-use workspace::Workspace;
+use postage::watch;
+use std::sync::Arc;
+use workspace::{ItemViewHandle, Settings, Toolbar, Workspace};
 
 action!(Deploy);
 
@@ -15,7 +18,11 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(FindBar::deploy);
 }
 
-struct FindBar;
+struct FindBar {
+    settings: watch::Receiver<Settings>,
+    query_editor: ViewHandle<Editor>,
+    active_editor: Option<ViewHandle<Editor>>,
+}
 
 impl Entity for FindBar {
     type Event = ();
@@ -27,19 +34,65 @@ impl View for FindBar {
     }
 
     fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
-        Empty::new()
+        ChildView::new(&self.query_editor)
             .contained()
-            .with_background_color(Color::red())
-            .constrained()
-            .with_height(30.)
+            .with_style(self.settings.borrow().theme.selector.input_editor.container)
             .boxed()
     }
 }
 
+impl Toolbar for FindBar {
+    fn active_item_changed(
+        &mut self,
+        item: Option<Box<dyn ItemViewHandle>>,
+        cx: &mut ViewContext<Self>,
+    ) -> bool {
+        self.active_editor = item.and_then(|item| item.act_as::<Editor>(cx));
+        self.active_editor.is_some()
+    }
+}
+
 impl FindBar {
+    fn new(settings: watch::Receiver<Settings>, cx: &mut ViewContext<Self>) -> Self {
+        let query_editor = cx.add_view(|cx| {
+            Editor::single_line(
+                {
+                    let settings = settings.clone();
+                    Arc::new(move |_| {
+                        let settings = settings.borrow();
+                        EditorSettings {
+                            style: settings.theme.selector.input_editor.as_editor(),
+                            tab_size: settings.tab_size,
+                            soft_wrap: editor::SoftWrap::None,
+                        }
+                    })
+                },
+                cx,
+            )
+        });
+        cx.subscribe(&query_editor, Self::on_query_editor_event)
+            .detach();
+
+        Self {
+            query_editor,
+            active_editor: None,
+            settings,
+        }
+    }
+
     fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
+        let settings = workspace.settings();
+        workspace.active_pane().update(cx, |pane, cx| {
+            pane.show_toolbar(cx, |cx| FindBar::new(settings, cx));
+            if let Some(toolbar) = pane.active_toolbar() {
+                cx.focus(toolbar);
+            }
+        });
+    }
+
+    fn cancel(workspace: &mut Workspace, _: &Cancel, cx: &mut ViewContext<Workspace>) {
         workspace
             .active_pane()
-            .update(cx, |pane, cx| pane.show_toolbar(cx, |_| FindBar));
+            .update(cx, |pane, cx| pane.hide_toolbar(cx));
     }
 }

crates/workspace/src/pane.rs 🔗

@@ -81,14 +81,27 @@ pub struct Pane {
     active_item_index: usize,
     settings: watch::Receiver<Settings>,
     nav_history: Rc<RefCell<NavHistory>>,
-    toolbars: HashMap<TypeId, AnyViewHandle>,
-    active_toolbar: Option<AnyViewHandle>,
+    toolbars: HashMap<TypeId, Box<dyn ToolbarHandle>>,
+    active_toolbar_type: Option<TypeId>,
+    active_toolbar_visible: bool,
 }
 
-// #[derive(Debug, Eq, PartialEq)]
-// pub struct State {
-//     pub tabs: Vec<TabState>,
-// }
+pub trait Toolbar: View {
+    fn active_item_changed(
+        &mut self,
+        item: Option<Box<dyn ItemViewHandle>>,
+        cx: &mut ViewContext<Self>,
+    ) -> bool;
+}
+
+trait ToolbarHandle {
+    fn active_item_changed(
+        &self,
+        item: Option<Box<dyn ItemViewHandle>>,
+        cx: &mut MutableAppContext,
+    ) -> bool;
+    fn to_any(&self) -> AnyViewHandle;
+}
 
 pub struct ItemNavHistory {
     history: Rc<RefCell<NavHistory>>,
@@ -129,7 +142,8 @@ impl Pane {
             settings,
             nav_history: Default::default(),
             toolbars: Default::default(),
-            active_toolbar: Default::default(),
+            active_toolbar_type: Default::default(),
+            active_toolbar_visible: false,
         }
     }
 
@@ -301,6 +315,7 @@ impl Pane {
             if prev_active_item_ix != self.active_item_index {
                 self.item_views[prev_active_item_ix].1.deactivated(cx);
             }
+            self.update_active_toolbar(cx);
             self.focus_active_item(cx);
             cx.notify();
         }
@@ -354,15 +369,18 @@ impl Pane {
                 true
             }
         });
-        self.active_item_index = cmp::min(
-            self.active_item_index,
-            self.item_views.len().saturating_sub(1),
+        self.activate_item(
+            cmp::min(
+                self.active_item_index,
+                self.item_views.len().saturating_sub(1),
+            ),
+            cx,
         );
 
         if self.item_views.is_empty() {
+            self.update_active_toolbar(cx);
             cx.emit(Event::Remove);
         }
-        cx.notify();
     }
 
     fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
@@ -378,16 +396,46 @@ impl Pane {
     pub fn show_toolbar<F, V>(&mut self, cx: &mut ViewContext<Self>, build_toolbar: F)
     where
         F: FnOnce(&mut ViewContext<V>) -> V,
-        V: View,
+        V: Toolbar,
     {
-        let handle = self
-            .toolbars
-            .entry(TypeId::of::<V>())
-            .or_insert_with(|| cx.add_view(build_toolbar).into());
-        self.active_toolbar = Some(handle.clone());
+        let type_id = TypeId::of::<V>();
+        let active_item = self.active_item();
+        self.toolbars
+            .entry(type_id)
+            .or_insert_with(|| Box::new(cx.add_view(build_toolbar)));
+        self.active_toolbar_type = Some(type_id);
+        self.active_toolbar_visible = self.toolbars[&type_id].active_item_changed(active_item, cx);
+        cx.notify();
+    }
+
+    pub fn hide_toolbar(&mut self, cx: &mut ViewContext<Self>) {
+        self.active_toolbar_type = None;
+        self.active_toolbar_visible = false;
+        self.focus_active_item(cx);
         cx.notify();
     }
 
+    pub fn active_toolbar(&self) -> Option<AnyViewHandle> {
+        let type_id = self.active_toolbar_type?;
+        let toolbar = self.toolbars.get(&type_id)?;
+        if self.active_toolbar_visible {
+            Some(toolbar.to_any())
+        } else {
+            None
+        }
+    }
+
+    fn update_active_toolbar(&mut self, cx: &mut ViewContext<Self>) {
+        if let Some(type_id) = self.active_toolbar_type {
+            if let Some(toolbar) = self.toolbars.get(&type_id) {
+                self.active_toolbar_visible = toolbar.active_item_changed(
+                    Some(self.item_views[self.active_item_index].1.clone()),
+                    cx,
+                );
+            }
+        }
+    }
+
     fn render_tabs(&self, cx: &mut RenderContext<Self>) -> ElementBox {
         let settings = self.settings.borrow();
         let theme = &settings.theme;
@@ -540,7 +588,7 @@ impl View for Pane {
             Flex::column()
                 .with_child(self.render_tabs(cx))
                 .with_children(
-                    self.active_toolbar
+                    self.active_toolbar()
                         .as_ref()
                         .map(|view| ChildView::new(view).boxed()),
                 )
@@ -556,6 +604,20 @@ impl View for Pane {
     }
 }
 
+impl<T: Toolbar> ToolbarHandle for ViewHandle<T> {
+    fn active_item_changed(
+        &self,
+        item: Option<Box<dyn ItemViewHandle>>,
+        cx: &mut MutableAppContext,
+    ) -> bool {
+        self.update(cx, |this, cx| this.active_item_changed(item, cx))
+    }
+
+    fn to_any(&self) -> AnyViewHandle {
+        self.into()
+    }
+}
+
 impl ItemNavHistory {
     pub fn new<T: ItemView>(history: Rc<RefCell<NavHistory>>, item_view: &ViewHandle<T>) -> Self {
         Self {