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}