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