selector.rs

  1use editor::Editor;
  2use gpui::{
  3    elements::{
  4        ChildView, Flex, FlexItem, Label, ParentElement, ScrollTarget, UniformList,
  5        UniformListState,
  6    },
  7    keymap, AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, Task,
  8    View, ViewContext, ViewHandle, WeakViewHandle,
  9};
 10use settings::Settings;
 11use std::cmp;
 12use workspace::menu::{Cancel, Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
 13
 14pub fn init<D: SelectorModalDelegate>(cx: &mut MutableAppContext) {
 15    cx.add_action(SelectorModal::<D>::select_first);
 16    cx.add_action(SelectorModal::<D>::select_last);
 17    cx.add_action(SelectorModal::<D>::select_next);
 18    cx.add_action(SelectorModal::<D>::select_prev);
 19    cx.add_action(SelectorModal::<D>::confirm);
 20    cx.add_action(SelectorModal::<D>::cancel);
 21}
 22
 23pub struct SelectorModal<D: SelectorModalDelegate> {
 24    delegate: WeakViewHandle<D>,
 25    query_editor: ViewHandle<Editor>,
 26    list_state: UniformListState,
 27}
 28
 29pub trait SelectorModalDelegate: View {
 30    fn match_count(&self) -> usize;
 31    fn selected_index(&self) -> usize;
 32    fn set_selected_index(&mut self, ix: usize);
 33    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()>;
 34    fn confirm(&mut self, cx: &mut ViewContext<Self>);
 35    fn dismiss(&mut self, cx: &mut ViewContext<Self>);
 36    fn render_match(&self, ix: usize, selected: bool, cx: &AppContext) -> ElementBox;
 37}
 38
 39impl<D: SelectorModalDelegate> Entity for SelectorModal<D> {
 40    type Event = ();
 41}
 42
 43impl<D: SelectorModalDelegate> View for SelectorModal<D> {
 44    fn ui_name() -> &'static str {
 45        "SelectorModal"
 46    }
 47
 48    fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
 49        let settings = cx.global::<Settings>();
 50        Flex::new(Axis::Vertical)
 51            .with_child(
 52                ChildView::new(&self.query_editor)
 53                    .contained()
 54                    .with_style(settings.theme.selector.input_editor.container)
 55                    .boxed(),
 56            )
 57            .with_child(
 58                FlexItem::new(self.render_matches(cx))
 59                    .flex(1., false)
 60                    .boxed(),
 61            )
 62            .contained()
 63            .with_style(settings.theme.selector.container)
 64            .constrained()
 65            .with_max_width(500.0)
 66            .with_max_height(420.0)
 67            .aligned()
 68            .top()
 69            .named("selector")
 70    }
 71
 72    fn keymap_context(&self, _: &AppContext) -> keymap::Context {
 73        let mut cx = Self::default_keymap_context();
 74        cx.set.insert("menu".into());
 75        cx
 76    }
 77
 78    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
 79        cx.focus(&self.query_editor);
 80    }
 81}
 82
 83impl<D: SelectorModalDelegate> SelectorModal<D> {
 84    pub fn new(delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self {
 85        let query_editor = cx.add_view(|cx| {
 86            Editor::single_line(Some(|theme| theme.selector.input_editor.clone()), cx)
 87        });
 88        cx.subscribe(&query_editor, Self::on_query_editor_event)
 89            .detach();
 90
 91        Self {
 92            delegate,
 93            query_editor,
 94            list_state: Default::default(),
 95        }
 96    }
 97
 98    fn render_matches(&self, cx: &AppContext) -> ElementBox {
 99        let delegate = self.delegate.clone();
100        let match_count = if let Some(delegate) = delegate.upgrade(cx) {
101            delegate.read(cx).match_count()
102        } else {
103            0
104        };
105
106        if match_count == 0 {
107            let settings = cx.global::<Settings>();
108            return Label::new(
109                "No matches".into(),
110                settings.theme.selector.empty.label.clone(),
111            )
112            .contained()
113            .with_style(settings.theme.selector.empty.container)
114            .named("empty matches");
115        }
116
117        UniformList::new(
118            self.list_state.clone(),
119            match_count,
120            move |mut range, items, cx| {
121                let cx = cx.as_ref();
122                let delegate = delegate.upgrade(cx).unwrap();
123                let delegate = delegate.read(cx);
124                let selected_ix = delegate.selected_index();
125                range.end = cmp::min(range.end, delegate.match_count());
126                items.extend(range.map(move |ix| delegate.render_match(ix, ix == selected_ix, cx)));
127            },
128        )
129        .contained()
130        .with_margin_top(6.0)
131        .named("matches")
132    }
133
134    fn on_query_editor_event(
135        &mut self,
136        _: ViewHandle<Editor>,
137        event: &editor::Event,
138        cx: &mut ViewContext<Self>,
139    ) {
140        if let Some(delegate) = self.delegate.upgrade(cx) {
141            match event {
142                editor::Event::BufferEdited { .. } => {
143                    let query = self.query_editor.read(cx).text(cx);
144                    let update = delegate.update(cx, |d, cx| d.update_matches(query, cx));
145                    cx.spawn(|this, mut cx| async move {
146                        update.await;
147                        this.update(&mut cx, |_, cx| cx.notify());
148                    })
149                    .detach();
150                }
151                editor::Event::Blurred => delegate.update(cx, |delegate, cx| {
152                    delegate.dismiss(cx);
153                }),
154                _ => {}
155            }
156        }
157    }
158
159    fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
160        if let Some(delegate) = self.delegate.upgrade(cx) {
161            let index = 0;
162            delegate.update(cx, |delegate, _| delegate.set_selected_index(0));
163            self.list_state.scroll_to(ScrollTarget::Show(index));
164            cx.notify();
165        }
166    }
167
168    fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
169        if let Some(delegate) = self.delegate.upgrade(cx) {
170            let index = delegate.update(cx, |delegate, _| {
171                let match_count = delegate.match_count();
172                let index = if match_count > 0 { match_count - 1 } else { 0 };
173                delegate.set_selected_index(index);
174                index
175            });
176            self.list_state.scroll_to(ScrollTarget::Show(index));
177            cx.notify();
178        }
179    }
180
181    fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
182        if let Some(delegate) = self.delegate.upgrade(cx) {
183            let index = delegate.update(cx, |delegate, _| {
184                let mut selected_index = delegate.selected_index();
185                if selected_index + 1 < delegate.match_count() {
186                    selected_index += 1;
187                    delegate.set_selected_index(selected_index);
188                }
189                selected_index
190            });
191            self.list_state.scroll_to(ScrollTarget::Show(index));
192            cx.notify();
193        }
194    }
195
196    fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
197        if let Some(delegate) = self.delegate.upgrade(cx) {
198            let index = delegate.update(cx, |delegate, _| {
199                let mut selected_index = delegate.selected_index();
200                if selected_index > 0 {
201                    selected_index -= 1;
202                    delegate.set_selected_index(selected_index);
203                }
204                selected_index
205            });
206            self.list_state.scroll_to(ScrollTarget::Show(index));
207            cx.notify();
208        }
209    }
210
211    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
212        if let Some(delegate) = self.delegate.upgrade(cx) {
213            delegate.update(cx, |delegate, cx| delegate.confirm(cx));
214        }
215    }
216
217    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
218        if let Some(delegate) = self.delegate.upgrade(cx) {
219            delegate.update(cx, |delegate, cx| delegate.dismiss(cx));
220        }
221    }
222}