Port quick_action_bar to zed2

Piotr Osiewicz and Nate created

Co-authored-by: Nate <nate@zed.dev>

Change summary

Cargo.lock                                       |  12 
Cargo.toml                                       |   1 
crates/quick_action_bar2/Cargo.toml              |  22 +
crates/quick_action_bar2/src/quick_action_bar.rs | 285 ++++++++++++++++++
crates/zed2/Cargo.toml                           |   2 
crates/zed2/src/zed2.rs                          |  11 
6 files changed, 327 insertions(+), 6 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7074,6 +7074,17 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "quick_action_bar2"
+version = "0.1.0"
+dependencies = [
+ "editor2",
+ "gpui2",
+ "search2",
+ "ui2",
+ "workspace2",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.33"
@@ -11843,6 +11854,7 @@ dependencies = [
  "postage",
  "project2",
  "project_panel2",
+ "quick_action_bar2",
  "rand 0.8.5",
  "regex",
  "rope2",

Cargo.toml 🔗

@@ -89,6 +89,7 @@ members = [
     "crates/project_panel",
     "crates/project_panel2",
     "crates/project_symbols",
+    "crates/quick_action_bar2",
     "crates/recent_projects",
     "crates/rope",
     "crates/rpc",

crates/quick_action_bar2/Cargo.toml 🔗

@@ -0,0 +1,22 @@
+[package]
+name = "quick_action_bar2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/quick_action_bar.rs"
+doctest = false
+
+[dependencies]
+#assistant = { path = "../assistant" }
+editor = { package = "editor2", path = "../editor2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+search = { package = "search2", path = "../search2" }
+workspace = { package = "workspace2", path = "../workspace2" }
+ui = { package = "ui2", path = "../ui2" }
+
+[dev-dependencies]
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }

crates/quick_action_bar2/src/quick_action_bar.rs 🔗

@@ -0,0 +1,285 @@
+// use assistant::{assistant_panel::InlineAssist, AssistantPanel};
+use editor::Editor;
+
+use gpui::{
+    Action, Div, ElementId, EventEmitter, InteractiveElement, ParentElement, Render, Stateful,
+    Styled, Subscription, View, ViewContext, WeakView,
+};
+use search::{buffer_search, BufferSearchBar};
+use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip};
+use workspace::{
+    item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
+};
+
+pub struct QuickActionBar {
+    buffer_search_bar: View<BufferSearchBar>,
+    active_item: Option<Box<dyn ItemHandle>>,
+    _inlay_hints_enabled_subscription: Option<Subscription>,
+    workspace: WeakView<Workspace>,
+}
+
+impl QuickActionBar {
+    pub fn new(buffer_search_bar: View<BufferSearchBar>, workspace: &Workspace) -> Self {
+        Self {
+            buffer_search_bar,
+            active_item: None,
+            _inlay_hints_enabled_subscription: None,
+            workspace: workspace.weak_handle(),
+        }
+    }
+
+    fn active_editor(&self) -> Option<View<Editor>> {
+        self.active_item
+            .as_ref()
+            .and_then(|item| item.downcast::<Editor>())
+    }
+}
+
+impl Render for QuickActionBar {
+    type Element = Stateful<Div>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        let search_button = QuickActionBarButton::new(
+            "toggle buffer search",
+            Icon::MagnifyingGlass,
+            !self.buffer_search_bar.read(cx).is_dismissed(),
+            Box::new(search::buffer_search::Deploy { focus: false }),
+            "Buffer Search",
+        );
+        let assistant_button = QuickActionBarButton::new(
+            "toggle inline assitant",
+            Icon::MagicWand,
+            false,
+            Box::new(gpui::NoAction),
+            "Inline assistant",
+        );
+        h_stack()
+            .id("quick action bar")
+            .p_1()
+            .gap_2()
+            .child(search_button)
+            .child(
+                div()
+                    .border()
+                    .border_color(gpui::red())
+                    .child(assistant_button),
+            )
+    }
+}
+
+impl EventEmitter<ToolbarItemEvent> for QuickActionBar {}
+
+// impl View for QuickActionBar {
+//     fn ui_name() -> &'static str {
+//         "QuickActionsBar"
+//     }
+
+//     fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
+//         let Some(editor) = self.active_editor() else {
+//             return div();
+//         };
+
+//         let mut bar = Flex::row();
+//         if editor.read(cx).supports_inlay_hints(cx) {
+//             bar = bar.with_child(render_quick_action_bar_button(
+//                 0,
+//                 "icons/inlay_hint.svg",
+//                 editor.read(cx).inlay_hints_enabled(),
+//                 (
+//                     "Toggle Inlay Hints".to_string(),
+//                     Some(Box::new(editor::ToggleInlayHints)),
+//                 ),
+//                 cx,
+//                 |this, cx| {
+//                     if let Some(editor) = this.active_editor() {
+//                         editor.update(cx, |editor, cx| {
+//                             editor.toggle_inlay_hints(&editor::ToggleInlayHints, cx);
+//                         });
+//                     }
+//                 },
+//             ));
+//         }
+
+//         if editor.read(cx).buffer().read(cx).is_singleton() {
+//             let search_bar_shown = !self.buffer_search_bar.read(cx).is_dismissed();
+//             let search_action = buffer_search::Deploy { focus: true };
+
+//             bar = bar.with_child(render_quick_action_bar_button(
+//                 1,
+//                 "icons/magnifying_glass.svg",
+//                 search_bar_shown,
+//                 (
+//                     "Buffer Search".to_string(),
+//                     Some(Box::new(search_action.clone())),
+//                 ),
+//                 cx,
+//                 move |this, cx| {
+//                     this.buffer_search_bar.update(cx, |buffer_search_bar, cx| {
+//                         if search_bar_shown {
+//                             buffer_search_bar.dismiss(&buffer_search::Dismiss, cx);
+//                         } else {
+//                             buffer_search_bar.deploy(&search_action, cx);
+//                         }
+//                     });
+//                 },
+//             ));
+//         }
+
+//         bar.add_child(render_quick_action_bar_button(
+//             2,
+//             "icons/magic-wand.svg",
+//             false,
+//             ("Inline Assist".into(), Some(Box::new(InlineAssist))),
+//             cx,
+//             move |this, cx| {
+//                 if let Some(workspace) = this.workspace.upgrade(cx) {
+//                     workspace.update(cx, |workspace, cx| {
+//                         AssistantPanel::inline_assist(workspace, &Default::default(), cx);
+//                     });
+//                 }
+//             },
+//         ));
+
+//         bar.into_any()
+//     }
+// }
+
+#[derive(IntoElement)]
+struct QuickActionBarButton {
+    id: ElementId,
+    icon: Icon,
+    toggled: bool,
+    action: Box<dyn Action>,
+    tooltip: SharedString,
+    tooltip_meta: Option<SharedString>,
+}
+
+impl QuickActionBarButton {
+    fn new(
+        id: impl Into<ElementId>,
+        icon: Icon,
+        toggled: bool,
+        action: Box<dyn Action>,
+        tooltip: impl Into<SharedString>,
+    ) -> Self {
+        Self {
+            id: id.into(),
+            icon,
+            toggled,
+            action,
+            tooltip: tooltip.into(),
+            tooltip_meta: None,
+        }
+    }
+
+    pub fn meta(mut self, meta: Option<impl Into<SharedString>>) -> Self {
+        self.tooltip_meta = meta.map(|meta| meta.into());
+        self
+    }
+}
+
+impl RenderOnce for QuickActionBarButton {
+    type Rendered = IconButton;
+
+    fn render(self, _: &mut WindowContext) -> Self::Rendered {
+        let tooltip = self.tooltip.clone();
+        let action = self.action.boxed_clone();
+        let tooltip_meta = self.tooltip_meta.clone();
+
+        IconButton::new(self.id.clone(), self.icon)
+            .size(ButtonSize::Compact)
+            .icon_size(IconSize::Small)
+            .style(ButtonStyle::Subtle)
+            .selected(self.toggled)
+            .tooltip(move |cx| {
+                if let Some(meta) = &tooltip_meta {
+                    Tooltip::with_meta(tooltip.clone(), Some(&*action), meta.clone(), cx)
+                } else {
+                    Tooltip::for_action(tooltip.clone(), &*action, cx)
+                }
+            })
+            .on_click({
+                let action = self.action.boxed_clone();
+                move |_, cx| cx.dispatch_action(action.boxed_clone())
+            })
+    }
+}
+
+// fn render_quick_action_bar_button<
+//     F: 'static + Fn(&mut QuickActionBar, &mut ViewContext<QuickActionBar>),
+// >(
+//     index: usize,
+//     icon: &'static str,
+//     toggled: bool,
+//     tooltip: (String, Option<Box<dyn Action>>),
+//     cx: &mut ViewContext<QuickActionBar>,
+//     on_click: F,
+// ) -> AnyElement<QuickActionBar> {
+//     enum QuickActionBarButton {}
+
+//     let theme = theme::current(cx);
+//     let (tooltip_text, action) = tooltip;
+
+//     MouseEventHandler::new::<QuickActionBarButton, _>(index, cx, |mouse_state, _| {
+//         let style = theme
+//             .workspace
+//             .toolbar
+//             .toggleable_tool
+//             .in_state(toggled)
+//             .style_for(mouse_state);
+//         Svg::new(icon)
+//             .with_color(style.color)
+//             .constrained()
+//             .with_width(style.icon_width)
+//             .aligned()
+//             .constrained()
+//             .with_width(style.button_width)
+//             .with_height(style.button_width)
+//             .contained()
+//             .with_style(style.container)
+//     })
+//     .with_cursor_style(CursorStyle::PointingHand)
+//     .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
+//     .with_tooltip::<QuickActionBarButton>(index, tooltip_text, action, theme.tooltip.clone(), cx)
+//     .into_any_named("quick action bar button")
+// }
+
+impl ToolbarItemView for QuickActionBar {
+    fn set_active_pane_item(
+        &mut self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        cx: &mut ViewContext<Self>,
+    ) -> ToolbarItemLocation {
+        match active_pane_item {
+            Some(active_item) => {
+                self.active_item = Some(active_item.boxed_clone());
+                self._inlay_hints_enabled_subscription.take();
+
+                if let Some(editor) = active_item.downcast::<Editor>() {
+                    let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
+                    let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
+                    self._inlay_hints_enabled_subscription =
+                        Some(cx.observe(&editor, move |_, editor, cx| {
+                            let editor = editor.read(cx);
+                            let new_inlay_hints_enabled = editor.inlay_hints_enabled();
+                            let new_supports_inlay_hints = editor.supports_inlay_hints(cx);
+                            let should_notify = inlay_hints_enabled != new_inlay_hints_enabled
+                                || supports_inlay_hints != new_supports_inlay_hints;
+                            inlay_hints_enabled = new_inlay_hints_enabled;
+                            supports_inlay_hints = new_supports_inlay_hints;
+                            if should_notify {
+                                cx.notify()
+                            }
+                        }));
+                    ToolbarItemLocation::PrimaryRight
+                } else {
+                    ToolbarItemLocation::Hidden
+                }
+            }
+            None => {
+                self.active_item = None;
+                ToolbarItemLocation::Hidden
+            }
+        }
+    }
+}

crates/zed2/Cargo.toml 🔗

@@ -55,7 +55,7 @@ outline = { package = "outline2", path = "../outline2" }
 project = { package = "project2", path = "../project2" }
 project_panel = { package = "project_panel2", path = "../project_panel2" }
 # project_symbols = { path = "../project_symbols" }
-# quick_action_bar = { path = "../quick_action_bar" }
+quick_action_bar = { package = "quick_action_bar2", path = "../quick_action_bar2" }
 # recent_projects = { path = "../recent_projects" }
 rope = { package = "rope2", path = "../rope2"}
 rpc = { package = "rpc2", path = "../rpc2" }

crates/zed2/src/zed2.rs 🔗

@@ -19,6 +19,7 @@ pub use open_listener::*;
 
 use anyhow::{anyhow, Context as _};
 use project_panel::ProjectPanel;
+use quick_action_bar::QuickActionBar;
 use settings::{initial_local_settings_content, Settings};
 use std::{borrow::Cow, ops::Deref, sync::Arc};
 use terminal_view::terminal_panel::TerminalPanel;
@@ -100,11 +101,11 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
                             toolbar.add_item(breadcrumbs, cx);
                             let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
                             toolbar.add_item(buffer_search_bar.clone(), cx);
-                            // todo!()
-                            //     let quick_action_bar = cx.add_view(|_| {
-                            //         QuickActionBar::new(buffer_search_bar, workspace)
-                            //     });
-                            //     toolbar.add_item(quick_action_bar, cx);
+
+                                let quick_action_bar = cx.build_view(|_| {
+                                    QuickActionBar::new(buffer_search_bar, workspace)
+                                });
+                                toolbar.add_item(quick_action_bar, cx);
                             let diagnostic_editor_controls =
                                 cx.build_view(|_| diagnostics::ToolbarControls::new());
                             //     toolbar.add_item(diagnostic_editor_controls, cx);