1use gpui::{Action, App, Context, Div, Entity, InteractiveElement, Window, div};
2use workspace::{Pane, Workspace};
3
4use crate::BufferSearchBar;
5
6/// Registrar inverts the dependency between search and its downstream user, allowing said downstream user to register search action without knowing exactly what those actions are.
7pub trait SearchActionsRegistrar {
8 fn register_handler<A: Action>(&mut self, callback: impl ActionExecutor<A>);
9}
10
11type SearchBarActionCallback<A> =
12 fn(&mut BufferSearchBar, &A, &mut Window, &mut Context<BufferSearchBar>);
13
14type GetSearchBar<T> =
15 for<'a, 'b> fn(&'a T, &'a mut Window, &mut Context<'b, T>) -> Option<Entity<BufferSearchBar>>;
16
17/// Registers search actions on a div that can be taken out.
18pub struct DivRegistrar<'a, 'b, T: 'static> {
19 div: Option<Div>,
20 cx: &'a mut Context<'b, T>,
21 search_getter: GetSearchBar<T>,
22}
23
24impl<'a, 'b, T: 'static> DivRegistrar<'a, 'b, T> {
25 pub fn new(search_getter: GetSearchBar<T>, cx: &'a mut Context<'b, T>) -> Self {
26 Self {
27 div: Some(div()),
28 cx,
29 search_getter,
30 }
31 }
32 pub fn into_div(self) -> Div {
33 // This option is always Some; it's an option in the first place because we want to call methods
34 // on div that require ownership.
35 self.div.unwrap()
36 }
37}
38
39impl<T: 'static> SearchActionsRegistrar for DivRegistrar<'_, '_, T> {
40 fn register_handler<A: Action>(&mut self, callback: impl ActionExecutor<A>) {
41 let getter = self.search_getter;
42 self.div = self.div.take().map(|div| {
43 div.on_action(self.cx.listener(move |this, action, window, cx| {
44 let should_notify = (getter)(this, window, cx)
45 .map(|search_bar| {
46 search_bar.update(cx, |search_bar, cx| {
47 callback.execute(search_bar, action, window, cx)
48 })
49 })
50 .unwrap_or(false);
51 if should_notify {
52 cx.notify();
53 } else {
54 cx.propagate();
55 }
56 }))
57 });
58 }
59}
60
61pub struct PaneDivRegistrar {
62 div: Option<Div>,
63 pane: Entity<Pane>,
64}
65
66impl PaneDivRegistrar {
67 pub fn new(div: Div, pane: Entity<Pane>) -> Self {
68 Self {
69 div: Some(div),
70 pane,
71 }
72 }
73
74 pub fn into_div(self) -> Div {
75 self.div.unwrap()
76 }
77}
78
79impl SearchActionsRegistrar for PaneDivRegistrar {
80 fn register_handler<A: Action>(&mut self, callback: impl ActionExecutor<A>) {
81 let pane = self.pane.clone();
82 self.div = self.div.take().map(|div| {
83 div.on_action(move |action: &A, window: &mut Window, cx: &mut App| {
84 let search_bar = pane
85 .read(cx)
86 .toolbar()
87 .read(cx)
88 .item_of_type::<BufferSearchBar>();
89 let should_notify = search_bar
90 .map(|search_bar| {
91 search_bar.update(cx, |search_bar, cx| {
92 callback.execute(search_bar, action, window, cx)
93 })
94 })
95 .unwrap_or(false);
96 if should_notify {
97 pane.update(cx, |_, cx| cx.notify());
98 } else {
99 cx.propagate();
100 }
101 })
102 });
103 }
104}
105
106pub fn register_pane_search_actions(div: Div, pane: Entity<Pane>) -> Div {
107 let mut registrar = PaneDivRegistrar::new(div, pane);
108 BufferSearchBar::register(&mut registrar);
109 registrar.into_div()
110}
111
112/// Register actions for an active pane.
113impl SearchActionsRegistrar for Workspace {
114 fn register_handler<A: Action>(&mut self, callback: impl ActionExecutor<A>) {
115 self.register_action(move |workspace, action: &A, window, cx| {
116 if workspace.has_active_modal(window, cx) && !workspace.hide_modal(window, cx) {
117 cx.propagate();
118 return;
119 }
120
121 let pane = workspace.active_pane();
122 let callback = callback.clone();
123 pane.update(cx, |this, cx| {
124 this.toolbar().update(cx, move |this, cx| {
125 if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
126 let should_notify = search_bar.update(cx, move |search_bar, cx| {
127 callback.execute(search_bar, action, window, cx)
128 });
129 if should_notify {
130 cx.notify();
131 } else {
132 cx.propagate();
133 }
134 }
135 })
136 });
137 });
138 }
139}
140
141type DidHandleAction = bool;
142/// Potentially executes the underlying action if some preconditions are met (e.g. buffer search bar is visible)
143pub trait ActionExecutor<A: Action>: 'static + Clone {
144 fn execute(
145 &self,
146 search_bar: &mut BufferSearchBar,
147 action: &A,
148 window: &mut Window,
149 cx: &mut Context<BufferSearchBar>,
150 ) -> DidHandleAction;
151}
152
153/// Run an action when the search bar has been dismissed from the panel.
154pub struct ForDismissed<A>(pub(super) SearchBarActionCallback<A>);
155impl<A> Clone for ForDismissed<A> {
156 fn clone(&self) -> Self {
157 Self(self.0)
158 }
159}
160
161impl<A: Action> ActionExecutor<A> for ForDismissed<A> {
162 fn execute(
163 &self,
164 search_bar: &mut BufferSearchBar,
165 action: &A,
166 window: &mut Window,
167 cx: &mut Context<BufferSearchBar>,
168 ) -> DidHandleAction {
169 if search_bar.is_dismissed() {
170 self.0(search_bar, action, window, cx);
171 true
172 } else {
173 false
174 }
175 }
176}
177
178/// Run an action when the search bar is deployed.
179pub struct ForDeployed<A>(pub(super) SearchBarActionCallback<A>);
180impl<A> Clone for ForDeployed<A> {
181 fn clone(&self) -> Self {
182 Self(self.0)
183 }
184}
185
186impl<A: Action> ActionExecutor<A> for ForDeployed<A> {
187 fn execute(
188 &self,
189 search_bar: &mut BufferSearchBar,
190 action: &A,
191 window: &mut Window,
192 cx: &mut Context<BufferSearchBar>,
193 ) -> DidHandleAction {
194 if search_bar.is_dismissed() || search_bar.active_searchable_item.is_none() {
195 false
196 } else {
197 self.0(search_bar, action, window, cx);
198 true
199 }
200 }
201}
202
203/// Run an action when the search bar has any matches or a pending external query,
204/// regardless of whether it is visible or not.
205pub struct WithResultsOrExternalQuery<A>(pub(super) SearchBarActionCallback<A>);
206impl<A> Clone for WithResultsOrExternalQuery<A> {
207 fn clone(&self) -> Self {
208 Self(self.0)
209 }
210}
211
212impl<A: Action> ActionExecutor<A> for WithResultsOrExternalQuery<A> {
213 fn execute(
214 &self,
215 search_bar: &mut BufferSearchBar,
216 action: &A,
217 window: &mut Window,
218 cx: &mut Context<BufferSearchBar>,
219 ) -> DidHandleAction {
220 #[cfg(not(target_os = "macos"))]
221 let has_external_query = false;
222
223 #[cfg(target_os = "macos")]
224 let has_external_query = search_bar.pending_external_query.is_some();
225
226 if has_external_query || search_bar.active_match_index.is_some() {
227 self.0(search_bar, action, window, cx);
228 true
229 } else {
230 false
231 }
232 }
233}