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}