registrar.rs

  1use gpui::{div, Action, Div, InteractiveElement, View, ViewContext};
  2use workspace::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> = fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>);
 12
 13type GetSearchBar<T> =
 14    for<'a, 'b> fn(&'a T, &'a mut ViewContext<'b, T>) -> Option<View<BufferSearchBar>>;
 15
 16/// Registers search actions on a div that can be taken out.
 17pub struct DivRegistrar<'a, 'b, T: 'static> {
 18    div: Option<Div>,
 19    cx: &'a mut ViewContext<'b, T>,
 20    search_getter: GetSearchBar<T>,
 21}
 22
 23impl<'a, 'b, T: 'static> DivRegistrar<'a, 'b, T> {
 24    pub fn new(search_getter: GetSearchBar<T>, cx: &'a mut ViewContext<'b, T>) -> Self {
 25        Self {
 26            div: Some(div()),
 27            cx,
 28            search_getter,
 29        }
 30    }
 31    pub fn into_div(self) -> Div {
 32        // This option is always Some; it's an option in the first place because we want to call methods
 33        // on div that require ownership.
 34        self.div.unwrap()
 35    }
 36}
 37
 38impl<T: 'static> SearchActionsRegistrar for DivRegistrar<'_, '_, T> {
 39    fn register_handler<A: Action>(&mut self, callback: impl ActionExecutor<A>) {
 40        let getter = self.search_getter;
 41        self.div = self.div.take().map(|div| {
 42            div.on_action(self.cx.listener(move |this, action, cx| {
 43                let should_notify = (getter)(this, cx)
 44                    .clone()
 45                    .map(|search_bar| {
 46                        search_bar.update(cx, |search_bar, cx| {
 47                            callback.execute(search_bar, action, 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
 61/// Register actions for an active pane.
 62impl SearchActionsRegistrar for Workspace {
 63    fn register_handler<A: Action>(&mut self, callback: impl ActionExecutor<A>) {
 64        self.register_action(move |workspace, action: &A, cx| {
 65            if workspace.has_active_modal(cx) {
 66                cx.propagate();
 67                return;
 68            }
 69
 70            let pane = workspace.active_pane();
 71            let callback = callback.clone();
 72            pane.update(cx, |this, cx| {
 73                this.toolbar().update(cx, move |this, cx| {
 74                    if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
 75                        let should_notify = search_bar.update(cx, move |search_bar, cx| {
 76                            callback.execute(search_bar, action, cx)
 77                        });
 78                        if should_notify {
 79                            cx.notify();
 80                        } else {
 81                            cx.propagate();
 82                        }
 83                    }
 84                })
 85            });
 86        });
 87    }
 88}
 89
 90type DidHandleAction = bool;
 91/// Potentially executes the underlying action if some preconditions are met (e.g. buffer search bar is visible)
 92pub trait ActionExecutor<A: Action>: 'static + Clone {
 93    fn execute(
 94        &self,
 95        search_bar: &mut BufferSearchBar,
 96        action: &A,
 97        cx: &mut ViewContext<BufferSearchBar>,
 98    ) -> DidHandleAction;
 99}
100
101/// Run an action when the search bar has been dismissed from the panel.
102pub struct ForDismissed<A>(pub(super) SearchBarActionCallback<A>);
103impl<A> Clone for ForDismissed<A> {
104    fn clone(&self) -> Self {
105        Self(self.0)
106    }
107}
108
109impl<A: Action> ActionExecutor<A> for ForDismissed<A> {
110    fn execute(
111        &self,
112        search_bar: &mut BufferSearchBar,
113        action: &A,
114        cx: &mut ViewContext<BufferSearchBar>,
115    ) -> DidHandleAction {
116        if search_bar.is_dismissed() {
117            self.0(search_bar, action, cx);
118            true
119        } else {
120            false
121        }
122    }
123}
124
125/// Run an action when the search bar is deployed.
126pub struct ForDeployed<A>(pub(super) SearchBarActionCallback<A>);
127impl<A> Clone for ForDeployed<A> {
128    fn clone(&self) -> Self {
129        Self(self.0)
130    }
131}
132
133impl<A: Action> ActionExecutor<A> for ForDeployed<A> {
134    fn execute(
135        &self,
136        search_bar: &mut BufferSearchBar,
137        action: &A,
138        cx: &mut ViewContext<BufferSearchBar>,
139    ) -> DidHandleAction {
140        if search_bar.is_dismissed() || search_bar.active_searchable_item.is_none() {
141            false
142        } else {
143            self.0(search_bar, action, cx);
144            true
145        }
146    }
147}
148
149/// Run an action when the search bar has any matches, regardless of whether it
150/// is visible or not.
151pub struct WithResults<A>(pub(super) SearchBarActionCallback<A>);
152impl<A> Clone for WithResults<A> {
153    fn clone(&self) -> Self {
154        Self(self.0)
155    }
156}
157
158impl<A: Action> ActionExecutor<A> for WithResults<A> {
159    fn execute(
160        &self,
161        search_bar: &mut BufferSearchBar,
162        action: &A,
163        cx: &mut ViewContext<BufferSearchBar>,
164    ) -> DidHandleAction {
165        if search_bar.active_match_index.is_some() {
166            self.0(search_bar, action, cx);
167            true
168        } else {
169            false
170        }
171    }
172}