quick_action_bar.rs

  1use assistant::{assistant_panel::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 mut bar = Flex::row();
 52        if editor.read(cx).supports_inlay_hints(cx) {
 53            bar = bar.with_child(render_quick_action_bar_button(
 54                0,
 55                "icons/inlay_hint.svg",
 56                editor.read(cx).inlay_hints_enabled(),
 57                (
 58                    "Toggle Inlay Hints".to_string(),
 59                    Some(Box::new(editor::ToggleInlayHints)),
 60                ),
 61                cx,
 62                |this, cx| {
 63                    if let Some(editor) = this.active_editor() {
 64                        editor.update(cx, |editor, cx| {
 65                            editor.toggle_inlay_hints(&editor::ToggleInlayHints, cx);
 66                        });
 67                    }
 68                },
 69            ));
 70        }
 71
 72        if editor.read(cx).buffer().read(cx).is_singleton() {
 73            let search_bar_shown = !self.buffer_search_bar.read(cx).is_dismissed();
 74            let search_action = buffer_search::Deploy { focus: true };
 75
 76            bar = bar.with_child(render_quick_action_bar_button(
 77                1,
 78                "icons/magnifying_glass.svg",
 79                search_bar_shown,
 80                (
 81                    "Buffer Search".to_string(),
 82                    Some(Box::new(search_action.clone())),
 83                ),
 84                cx,
 85                move |this, cx| {
 86                    this.buffer_search_bar.update(cx, |buffer_search_bar, cx| {
 87                        if search_bar_shown {
 88                            buffer_search_bar.dismiss(&buffer_search::Dismiss, cx);
 89                        } else {
 90                            buffer_search_bar.deploy(&search_action, cx);
 91                        }
 92                    });
 93                },
 94            ));
 95        }
 96
 97        bar.add_child(render_quick_action_bar_button(
 98            2,
 99            "icons/magic-wand.svg",
100            false,
101            ("Inline Assist".into(), Some(Box::new(InlineAssist))),
102            cx,
103            move |this, cx| {
104                if let Some(workspace) = this.workspace.upgrade(cx) {
105                    workspace.update(cx, |workspace, cx| {
106                        AssistantPanel::inline_assist(workspace, &Default::default(), cx);
107                    });
108                }
109            },
110        ));
111
112        bar.into_any()
113    }
114}
115
116fn render_quick_action_bar_button<
117    F: 'static + Fn(&mut QuickActionBar, &mut EventContext<QuickActionBar>),
118>(
119    index: usize,
120    icon: &'static str,
121    toggled: bool,
122    tooltip: (String, Option<Box<dyn Action>>),
123    cx: &mut ViewContext<QuickActionBar>,
124    on_click: F,
125) -> AnyElement<QuickActionBar> {
126    enum QuickActionBarButton {}
127
128    let theme = theme::current(cx);
129    let (tooltip_text, action) = tooltip;
130
131    MouseEventHandler::new::<QuickActionBarButton, _>(index, cx, |mouse_state, _| {
132        let style = theme
133            .workspace
134            .toolbar
135            .toggleable_tool
136            .in_state(toggled)
137            .style_for(mouse_state);
138        Svg::new(icon)
139            .with_color(style.color)
140            .constrained()
141            .with_width(style.icon_width)
142            .aligned()
143            .constrained()
144            .with_width(style.button_width)
145            .with_height(style.button_width)
146            .contained()
147            .with_style(style.container)
148    })
149    .with_cursor_style(CursorStyle::PointingHand)
150    .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx))
151    .with_tooltip::<QuickActionBarButton>(index, tooltip_text, action, theme.tooltip.clone(), cx)
152    .into_any_named("quick action bar button")
153}
154
155impl ToolbarItemView for QuickActionBar {
156    fn set_active_pane_item(
157        &mut self,
158        active_pane_item: Option<&dyn ItemHandle>,
159        cx: &mut ViewContext<Self>,
160    ) -> ToolbarItemLocation {
161        match active_pane_item {
162            Some(active_item) => {
163                self.active_item = Some(active_item.boxed_clone());
164                self._inlay_hints_enabled_subscription.take();
165
166                if let Some(editor) = active_item.downcast::<Editor>() {
167                    let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
168                    let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
169                    self._inlay_hints_enabled_subscription =
170                        Some(cx.observe(&editor, move |_, editor, cx| {
171                            let editor = editor.read(cx);
172                            let new_inlay_hints_enabled = editor.inlay_hints_enabled();
173                            let new_supports_inlay_hints = editor.supports_inlay_hints(cx);
174                            let should_notify = inlay_hints_enabled != new_inlay_hints_enabled
175                                || supports_inlay_hints != new_supports_inlay_hints;
176                            inlay_hints_enabled = new_inlay_hints_enabled;
177                            supports_inlay_hints = new_supports_inlay_hints;
178                            if should_notify {
179                                cx.notify()
180                            }
181                        }));
182                    ToolbarItemLocation::PrimaryRight { flex: None }
183                } else {
184                    ToolbarItemLocation::Hidden
185                }
186            }
187            None => {
188                self.active_item = None;
189                ToolbarItemLocation::Hidden
190            }
191        }
192    }
193}