quick_action_bar.rs

  1use assistant::{AssistantPanel, InlineAssist};
  2use editor::Editor;
  3
  4use gpui::{
  5    Action, ClickEvent, ElementId, EventEmitter, InteractiveElement, ParentElement, Render, Styled,
  6    Subscription, View, ViewContext, WeakView,
  7};
  8use search::{buffer_search, BufferSearchBar};
  9use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip};
 10use workspace::{
 11    item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
 12};
 13
 14pub struct QuickActionBar {
 15    buffer_search_bar: View<BufferSearchBar>,
 16    active_item: Option<Box<dyn ItemHandle>>,
 17    _inlay_hints_enabled_subscription: Option<Subscription>,
 18    workspace: WeakView<Workspace>,
 19}
 20
 21impl QuickActionBar {
 22    pub fn new(buffer_search_bar: View<BufferSearchBar>, workspace: &Workspace) -> Self {
 23        Self {
 24            buffer_search_bar,
 25            active_item: None,
 26            _inlay_hints_enabled_subscription: None,
 27            workspace: workspace.weak_handle(),
 28        }
 29    }
 30
 31    fn active_editor(&self) -> Option<View<Editor>> {
 32        self.active_item
 33            .as_ref()
 34            .and_then(|item| item.downcast::<Editor>())
 35    }
 36}
 37
 38impl Render for QuickActionBar {
 39    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 40        let Some(editor) = self.active_editor() else {
 41            return div().id("empty quick action bar");
 42        };
 43
 44        let inlay_hints_button = Some(QuickActionBarButton::new(
 45            "toggle inlay hints",
 46            IconName::InlayHint,
 47            editor.read(cx).inlay_hints_enabled(),
 48            Box::new(editor::actions::ToggleInlayHints),
 49            "Toggle Inlay Hints",
 50            {
 51                let editor = editor.clone();
 52                move |_, cx| {
 53                    editor.update(cx, |editor, cx| {
 54                        editor.toggle_inlay_hints(&editor::actions::ToggleInlayHints, cx);
 55                    });
 56                }
 57            },
 58        ))
 59        .filter(|_| editor.read(cx).supports_inlay_hints(cx));
 60
 61        let search_button = Some(QuickActionBarButton::new(
 62            "toggle buffer search",
 63            IconName::MagnifyingGlass,
 64            !self.buffer_search_bar.read(cx).is_dismissed(),
 65            Box::new(buffer_search::Deploy { focus: false }),
 66            "Buffer Search",
 67            {
 68                let buffer_search_bar = self.buffer_search_bar.clone();
 69                move |_, cx| {
 70                    buffer_search_bar.update(cx, |search_bar, cx| {
 71                        search_bar.toggle(&buffer_search::Deploy { focus: true }, cx)
 72                    });
 73                }
 74            },
 75        ))
 76        .filter(|_| editor.is_singleton(cx));
 77
 78        let assistant_button = QuickActionBarButton::new(
 79            "toggle inline assistant",
 80            IconName::MagicWand,
 81            false,
 82            Box::new(InlineAssist),
 83            "Inline Assist",
 84            {
 85                let workspace = self.workspace.clone();
 86                move |_, cx| {
 87                    if let Some(workspace) = workspace.upgrade() {
 88                        workspace.update(cx, |workspace, cx| {
 89                            AssistantPanel::inline_assist(workspace, &InlineAssist, cx);
 90                        });
 91                    }
 92                }
 93            },
 94        );
 95
 96        h_flex()
 97            .id("quick action bar")
 98            .gap_2()
 99            .children(inlay_hints_button)
100            .children(search_button)
101            .child(assistant_button)
102    }
103}
104
105impl EventEmitter<ToolbarItemEvent> for QuickActionBar {}
106
107#[derive(IntoElement)]
108struct QuickActionBarButton {
109    id: ElementId,
110    icon: IconName,
111    toggled: bool,
112    action: Box<dyn Action>,
113    tooltip: SharedString,
114    on_click: Box<dyn Fn(&ClickEvent, &mut WindowContext)>,
115}
116
117impl QuickActionBarButton {
118    fn new(
119        id: impl Into<ElementId>,
120        icon: IconName,
121        toggled: bool,
122        action: Box<dyn Action>,
123        tooltip: impl Into<SharedString>,
124        on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
125    ) -> Self {
126        Self {
127            id: id.into(),
128            icon,
129            toggled,
130            action,
131            tooltip: tooltip.into(),
132            on_click: Box::new(on_click),
133        }
134    }
135}
136
137impl RenderOnce for QuickActionBarButton {
138    fn render(self, _: &mut WindowContext) -> impl IntoElement {
139        let tooltip = self.tooltip.clone();
140        let action = self.action.boxed_clone();
141
142        IconButton::new(self.id.clone(), self.icon)
143            .size(ButtonSize::Compact)
144            .icon_size(IconSize::Small)
145            .style(ButtonStyle::Subtle)
146            .selected(self.toggled)
147            .tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx))
148            .on_click(move |event, cx| (self.on_click)(event, cx))
149    }
150}
151
152impl ToolbarItemView for QuickActionBar {
153    fn set_active_pane_item(
154        &mut self,
155        active_pane_item: Option<&dyn ItemHandle>,
156        cx: &mut ViewContext<Self>,
157    ) -> ToolbarItemLocation {
158        match active_pane_item {
159            Some(active_item) => {
160                self.active_item = Some(active_item.boxed_clone());
161                self._inlay_hints_enabled_subscription.take();
162
163                if let Some(editor) = active_item.downcast::<Editor>() {
164                    let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
165                    let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
166                    self._inlay_hints_enabled_subscription =
167                        Some(cx.observe(&editor, move |_, editor, cx| {
168                            let editor = editor.read(cx);
169                            let new_inlay_hints_enabled = editor.inlay_hints_enabled();
170                            let new_supports_inlay_hints = editor.supports_inlay_hints(cx);
171                            let should_notify = inlay_hints_enabled != new_inlay_hints_enabled
172                                || supports_inlay_hints != new_supports_inlay_hints;
173                            inlay_hints_enabled = new_inlay_hints_enabled;
174                            supports_inlay_hints = new_supports_inlay_hints;
175                            if should_notify {
176                                cx.notify()
177                            }
178                        }));
179                    ToolbarItemLocation::PrimaryRight
180                } else {
181                    ToolbarItemLocation::Hidden
182                }
183            }
184            None => {
185                self.active_item = None;
186                ToolbarItemLocation::Hidden
187            }
188        }
189    }
190}