1use assistant::assistant_settings::AssistantSettings;
2use assistant::{AssistantPanel, InlineAssist};
3use editor::{Editor, EditorSettings};
4
5use gpui::{
6 anchored, deferred, Action, AnchorCorner, ClickEvent, DismissEvent, ElementId, EventEmitter,
7 InteractiveElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView,
8};
9use search::{buffer_search, BufferSearchBar};
10use settings::{Settings, SettingsStore};
11use ui::{
12 prelude::*, ButtonSize, ButtonStyle, ContextMenu, IconButton, IconName, IconSize, Tooltip,
13};
14use workspace::{
15 item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
16};
17
18pub struct QuickActionBar {
19 buffer_search_bar: View<BufferSearchBar>,
20 toggle_settings_menu: Option<View<ContextMenu>>,
21 active_item: Option<Box<dyn ItemHandle>>,
22 _inlay_hints_enabled_subscription: Option<Subscription>,
23 workspace: WeakView<Workspace>,
24 show: bool,
25}
26
27impl QuickActionBar {
28 pub fn new(
29 buffer_search_bar: View<BufferSearchBar>,
30 workspace: &Workspace,
31 cx: &mut ViewContext<Self>,
32 ) -> Self {
33 let mut this = Self {
34 buffer_search_bar,
35 toggle_settings_menu: None,
36 active_item: None,
37 _inlay_hints_enabled_subscription: None,
38 workspace: workspace.weak_handle(),
39 show: true,
40 };
41 this.apply_settings(cx);
42 cx.observe_global::<SettingsStore>(|this, cx| this.apply_settings(cx))
43 .detach();
44 this
45 }
46
47 fn active_editor(&self) -> Option<View<Editor>> {
48 self.active_item
49 .as_ref()
50 .and_then(|item| item.downcast::<Editor>())
51 }
52
53 fn apply_settings(&mut self, cx: &mut ViewContext<Self>) {
54 let new_show = EditorSettings::get_global(cx).toolbar.quick_actions;
55 if new_show != self.show {
56 self.show = new_show;
57 cx.emit(ToolbarItemEvent::ChangeLocation(
58 self.get_toolbar_item_location(),
59 ));
60 }
61 }
62
63 fn get_toolbar_item_location(&self) -> ToolbarItemLocation {
64 if self.show && self.active_editor().is_some() {
65 ToolbarItemLocation::PrimaryRight
66 } else {
67 ToolbarItemLocation::Hidden
68 }
69 }
70
71 fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
72 div().absolute().bottom_0().right_0().size_0().child(
73 deferred(
74 anchored()
75 .anchor(AnchorCorner::TopRight)
76 .child(menu.clone()),
77 )
78 .with_priority(1),
79 )
80 }
81}
82
83impl Render for QuickActionBar {
84 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
85 let Some(editor) = self.active_editor() else {
86 return div().id("empty quick action bar");
87 };
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::find()),
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::find(), 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 let editor_settings_dropdown =
125 IconButton::new("toggle_editor_settings_icon", IconName::Sliders)
126 .size(ButtonSize::Compact)
127 .icon_size(IconSize::Small)
128 .style(ButtonStyle::Subtle)
129 .selected(self.toggle_settings_menu.is_some())
130 .on_click({
131 let editor = editor.clone();
132 cx.listener(move |quick_action_bar, _, cx| {
133 let inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
134 let supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
135 let git_blame_inline_enabled = editor.read(cx).git_blame_inline_enabled();
136
137 let menu = ContextMenu::build(cx, |mut menu, _| {
138 if supports_inlay_hints {
139 menu = menu.toggleable_entry(
140 "Show Inlay Hints",
141 inlay_hints_enabled,
142 Some(editor::actions::ToggleInlayHints.boxed_clone()),
143 {
144 let editor = editor.clone();
145 move |cx| {
146 editor.update(cx, |editor, cx| {
147 editor.toggle_inlay_hints(
148 &editor::actions::ToggleInlayHints,
149 cx,
150 );
151 });
152 }
153 },
154 );
155 }
156
157 menu = menu.toggleable_entry(
158 "Show Git Blame Inline",
159 git_blame_inline_enabled,
160 Some(editor::actions::ToggleGitBlameInline.boxed_clone()),
161 {
162 let editor = editor.clone();
163 move |cx| {
164 editor.update(cx, |editor, cx| {
165 editor.toggle_git_blame_inline(
166 &editor::actions::ToggleGitBlameInline,
167 cx,
168 )
169 });
170 }
171 },
172 );
173
174 menu
175 });
176 cx.subscribe(&menu, |quick_action_bar, _, _: &DismissEvent, _cx| {
177 quick_action_bar.toggle_settings_menu = None;
178 })
179 .detach();
180 quick_action_bar.toggle_settings_menu = Some(menu);
181 })
182 })
183 .when(self.toggle_settings_menu.is_none(), |this| {
184 this.tooltip(|cx| Tooltip::text("Editor Controls", cx))
185 });
186
187 h_flex()
188 .id("quick action bar")
189 .gap_3()
190 .child(
191 h_flex()
192 .gap_1p5()
193 .children(search_button)
194 .when(AssistantSettings::get_global(cx).button, |bar| {
195 bar.child(assistant_button)
196 }),
197 )
198 .child(editor_settings_dropdown)
199 .when_some(
200 self.toggle_settings_menu.as_ref(),
201 |el, toggle_settings_menu| {
202 el.child(Self::render_menu_overlay(toggle_settings_menu))
203 },
204 )
205 }
206}
207
208impl EventEmitter<ToolbarItemEvent> for QuickActionBar {}
209
210#[derive(IntoElement)]
211struct QuickActionBarButton {
212 id: ElementId,
213 icon: IconName,
214 toggled: bool,
215 action: Box<dyn Action>,
216 tooltip: SharedString,
217 on_click: Box<dyn Fn(&ClickEvent, &mut WindowContext)>,
218}
219
220impl QuickActionBarButton {
221 fn new(
222 id: impl Into<ElementId>,
223 icon: IconName,
224 toggled: bool,
225 action: Box<dyn Action>,
226 tooltip: impl Into<SharedString>,
227 on_click: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
228 ) -> Self {
229 Self {
230 id: id.into(),
231 icon,
232 toggled,
233 action,
234 tooltip: tooltip.into(),
235 on_click: Box::new(on_click),
236 }
237 }
238}
239
240impl RenderOnce for QuickActionBarButton {
241 fn render(self, _: &mut WindowContext) -> impl IntoElement {
242 let tooltip = self.tooltip.clone();
243 let action = self.action.boxed_clone();
244
245 IconButton::new(self.id.clone(), self.icon)
246 .size(ButtonSize::Compact)
247 .icon_size(IconSize::Small)
248 .style(ButtonStyle::Subtle)
249 .selected(self.toggled)
250 .tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx))
251 .on_click(move |event, cx| (self.on_click)(event, cx))
252 }
253}
254
255impl ToolbarItemView for QuickActionBar {
256 fn set_active_pane_item(
257 &mut self,
258 active_pane_item: Option<&dyn ItemHandle>,
259 cx: &mut ViewContext<Self>,
260 ) -> ToolbarItemLocation {
261 self.active_item = active_pane_item.map(ItemHandle::boxed_clone);
262 if let Some(active_item) = active_pane_item {
263 self._inlay_hints_enabled_subscription.take();
264
265 if let Some(editor) = active_item.downcast::<Editor>() {
266 let mut inlay_hints_enabled = editor.read(cx).inlay_hints_enabled();
267 let mut supports_inlay_hints = editor.read(cx).supports_inlay_hints(cx);
268 self._inlay_hints_enabled_subscription =
269 Some(cx.observe(&editor, move |_, editor, cx| {
270 let editor = editor.read(cx);
271 let new_inlay_hints_enabled = editor.inlay_hints_enabled();
272 let new_supports_inlay_hints = editor.supports_inlay_hints(cx);
273 let should_notify = inlay_hints_enabled != new_inlay_hints_enabled
274 || supports_inlay_hints != new_supports_inlay_hints;
275 inlay_hints_enabled = new_inlay_hints_enabled;
276 supports_inlay_hints = new_supports_inlay_hints;
277 if should_notify {
278 cx.notify()
279 }
280 }));
281 }
282 }
283 self.get_toolbar_item_location()
284 }
285}