registrar.rs

  1use gpui::{Action, Context, Div, Entity, InteractiveElement, Window, div};
  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> =
 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                    .clone()
 46                    .map(|search_bar| {
 47                        search_bar.update(cx, |search_bar, cx| {
 48                            callback.execute(search_bar, action, window, cx)
 49                        })
 50                    })
 51                    .unwrap_or(false);
 52                if should_notify {
 53                    cx.notify();
 54                } else {
 55                    cx.propagate();
 56                }
 57            }))
 58        });
 59    }
 60}
 61
 62/// Register actions for an active pane.
 63impl SearchActionsRegistrar for Workspace {
 64    fn register_handler<A: Action>(&mut self, callback: impl ActionExecutor<A>) {
 65        self.register_action(move |workspace, action: &A, window, cx| {
 66            if workspace.has_active_modal(window, cx) {
 67                cx.propagate();
 68                return;
 69            }
 70
 71            let pane = workspace.active_pane();
 72            let callback = callback.clone();
 73            pane.update(cx, |this, cx| {
 74                this.toolbar().update(cx, move |this, cx| {
 75                    if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
 76                        let should_notify = search_bar.update(cx, move |search_bar, cx| {
 77                            callback.execute(search_bar, action, window, cx)
 78                        });
 79                        if should_notify {
 80                            cx.notify();
 81                        } else {
 82                            cx.propagate();
 83                        }
 84                    }
 85                })
 86            });
 87        });
 88    }
 89}
 90
 91type DidHandleAction = bool;
 92/// Potentially executes the underlying action if some preconditions are met (e.g. buffer search bar is visible)
 93pub trait ActionExecutor<A: Action>: 'static + Clone {
 94    fn execute(
 95        &self,
 96        search_bar: &mut BufferSearchBar,
 97        action: &A,
 98        window: &mut Window,
 99        cx: &mut Context<BufferSearchBar>,
100    ) -> DidHandleAction;
101}
102
103/// Run an action when the search bar has been dismissed from the panel.
104pub struct ForDismissed<A>(pub(super) SearchBarActionCallback<A>);
105impl<A> Clone for ForDismissed<A> {
106    fn clone(&self) -> Self {
107        Self(self.0)
108    }
109}
110
111impl<A: Action> ActionExecutor<A> for ForDismissed<A> {
112    fn execute(
113        &self,
114        search_bar: &mut BufferSearchBar,
115        action: &A,
116        window: &mut Window,
117        cx: &mut Context<BufferSearchBar>,
118    ) -> DidHandleAction {
119        if search_bar.is_dismissed() {
120            self.0(search_bar, action, window, cx);
121            true
122        } else {
123            false
124        }
125    }
126}
127
128/// Run an action when the search bar is deployed.
129pub struct ForDeployed<A>(pub(super) SearchBarActionCallback<A>);
130impl<A> Clone for ForDeployed<A> {
131    fn clone(&self) -> Self {
132        Self(self.0)
133    }
134}
135
136impl<A: Action> ActionExecutor<A> for ForDeployed<A> {
137    fn execute(
138        &self,
139        search_bar: &mut BufferSearchBar,
140        action: &A,
141        window: &mut Window,
142        cx: &mut Context<BufferSearchBar>,
143    ) -> DidHandleAction {
144        if search_bar.is_dismissed() || search_bar.active_searchable_item.is_none() {
145            false
146        } else {
147            self.0(search_bar, action, window, cx);
148            true
149        }
150    }
151}
152
153/// Run an action when the search bar has any matches, regardless of whether it
154/// is visible or not.
155pub struct WithResults<A>(pub(super) SearchBarActionCallback<A>);
156impl<A> Clone for WithResults<A> {
157    fn clone(&self) -> Self {
158        Self(self.0)
159    }
160}
161
162impl<A: Action> ActionExecutor<A> for WithResults<A> {
163    fn execute(
164        &self,
165        search_bar: &mut BufferSearchBar,
166        action: &A,
167        window: &mut Window,
168        cx: &mut Context<BufferSearchBar>,
169    ) -> DidHandleAction {
170        if search_bar.active_match_index.is_some() {
171            self.0(search_bar, action, window, cx);
172            true
173        } else {
174            false
175        }
176    }
177}