quick_action_bar.rs

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