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