quick_action_bar.rs

  1use ai::{assistant::InlineAssist, AssistantPanel};
  2use editor::Editor;
  3use gpui::{
  4    elements::{Empty, Flex, MouseEventHandler, ParentElement, Svg},
  5    platform::{CursorStyle, MouseButton},
  6    Action, AnyElement, Element, Entity, EventContext, Subscription, View, ViewContext, ViewHandle,
  7    WeakViewHandle,
  8};
  9
 10use search::{buffer_search, BufferSearchBar};
 11use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView, Workspace};
 12
 13pub struct QuickActionBar {
 14    buffer_search_bar: ViewHandle<BufferSearchBar>,
 15    active_item: Option<Box<dyn ItemHandle>>,
 16    _inlay_hints_enabled_subscription: Option<Subscription>,
 17    workspace: WeakViewHandle<Workspace>,
 18}
 19
 20impl QuickActionBar {
 21    pub fn new(buffer_search_bar: ViewHandle<BufferSearchBar>, workspace: &Workspace) -> Self {
 22        Self {
 23            buffer_search_bar,
 24            active_item: None,
 25            _inlay_hints_enabled_subscription: None,
 26            workspace: workspace.weak_handle(),
 27        }
 28    }
 29
 30    fn active_editor(&self) -> Option<ViewHandle<Editor>> {
 31        self.active_item
 32            .as_ref()
 33            .and_then(|item| item.downcast::<Editor>())
 34    }
 35}
 36
 37impl Entity for QuickActionBar {
 38    type Event = ();
 39}
 40
 41impl View for QuickActionBar {
 42    fn ui_name() -> &'static str {
 43        "QuickActionsBar"
 44    }
 45
 46    fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
 47        let Some(editor) = self.active_editor() else {
 48            return Empty::new().into_any();
 49        };
 50
 51        let inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
 52        let mut bar = Flex::row().with_child(render_quick_action_bar_button(
 53            0,
 54            "icons/inlay_hint.svg",
 55            inlay_hints_enabled,
 56            (
 57                "Toggle Inlay Hints".to_string(),
 58                Some(Box::new(editor::ToggleInlayHints)),
 59            ),
 60            cx,
 61            |this, cx| {
 62                if let Some(editor) = this.active_editor() {
 63                    editor.update(cx, |editor, cx| {
 64                        editor.toggle_inlay_hints(&editor::ToggleInlayHints, cx);
 65                    });
 66                }
 67            },
 68        ));
 69
 70        if editor.read(cx).buffer().read(cx).is_singleton() {
 71            let search_bar_shown = !self.buffer_search_bar.read(cx).is_dismissed();
 72            let search_action = buffer_search::Deploy { focus: true };
 73
 74            bar = bar.with_child(render_quick_action_bar_button(
 75                1,
 76                "icons/magnifying_glass.svg",
 77                search_bar_shown,
 78                (
 79                    "Buffer Search".to_string(),
 80                    Some(Box::new(search_action.clone())),
 81                ),
 82                cx,
 83                move |this, cx| {
 84                    this.buffer_search_bar.update(cx, |buffer_search_bar, cx| {
 85                        if search_bar_shown {
 86                            buffer_search_bar.dismiss(&buffer_search::Dismiss, cx);
 87                        } else {
 88                            buffer_search_bar.deploy(&search_action, cx);
 89                        }
 90                    });
 91                },
 92            ));
 93        }
 94
 95        bar.add_child(render_quick_action_bar_button(
 96            2,
 97            "icons/radix/magic-wand.svg",
 98            false,
 99            ("Inline Assist".into(), Some(Box::new(InlineAssist))),
100            cx,
101            move |this, cx| {
102                if let Some(workspace) = this.workspace.upgrade(cx) {
103                    workspace.update(cx, |workspace, cx| {
104                        AssistantPanel::inline_assist(workspace, &Default::default(), cx);
105                    });
106                }
107            },
108        ));
109
110        bar.into_any()
111    }
112}
113
114fn render_quick_action_bar_button<
115    F: 'static + Fn(&mut QuickActionBar, &mut EventContext<QuickActionBar>),
116>(
117    index: usize,
118    icon: &'static str,
119    toggled: bool,
120    tooltip: (String, Option<Box<dyn Action>>),
121    cx: &mut ViewContext<QuickActionBar>,
122    on_click: F,
123) -> AnyElement<QuickActionBar> {
124    enum QuickActionBarButton {}
125
126    let theme = theme::current(cx);
127    let (tooltip_text, action) = tooltip;
128
129    MouseEventHandler::new::<QuickActionBarButton, _>(index, cx, |mouse_state, _| {
130        let style = theme
131            .workspace
132            .toolbar
133            .toggleable_tool
134            .in_state(toggled)
135            .style_for(mouse_state);
136        Svg::new(icon)
137            .with_color(style.color)
138            .constrained()
139            .with_width(style.icon_width)
140            .aligned()
141            .constrained()
142            .with_width(style.button_width)
143            .with_height(style.button_width)
144            .contained()
145            .with_style(style.container)
146    })
147    .with_cursor_style(CursorStyle::PointingHand)
148    .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
149    .with_tooltip::<QuickActionBarButton>(index, tooltip_text, action, theme.tooltip.clone(), cx)
150    .into_any_named("quick action bar button")
151}
152
153impl ToolbarItemView for QuickActionBar {
154    fn set_active_pane_item(
155        &mut self,
156        active_pane_item: Option<&dyn ItemHandle>,
157        cx: &mut ViewContext<Self>,
158    ) -> ToolbarItemLocation {
159        match active_pane_item {
160            Some(active_item) => {
161                self.active_item = Some(active_item.boxed_clone());
162                self._inlay_hints_enabled_subscription.take();
163
164                if let Some(editor) = active_item.downcast::<Editor>() {
165                    let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
166                    self._inlay_hints_enabled_subscription =
167                        Some(cx.observe(&editor, move |_, editor, cx| {
168                            let new_inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
169                            if inlay_hints_enabled != new_inlay_hints_enabled {
170                                inlay_hints_enabled = new_inlay_hints_enabled;
171                                cx.notify();
172                            }
173                        }));
174                    ToolbarItemLocation::PrimaryRight { flex: None }
175                } else {
176                    ToolbarItemLocation::Hidden
177                }
178            }
179            None => {
180                self.active_item = None;
181                ToolbarItemLocation::Hidden
182            }
183        }
184    }
185}