picker2.rs

  1use editor::Editor;
  2use gpui::{
  3    div, uniform_list, Component, Div, FocusEnabled, ParentElement, Render, StatefulInteractivity,
  4    StatelessInteractive, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
  5    WindowContext,
  6};
  7use std::cmp;
  8
  9pub struct Picker<D: PickerDelegate> {
 10    pub delegate: D,
 11    scroll_handle: UniformListScrollHandle,
 12    editor: View<Editor>,
 13    pending_update_matches: Option<Task<Option<()>>>,
 14}
 15
 16pub trait PickerDelegate: Sized + 'static {
 17    type ListItem: Component<Picker<Self>>;
 18
 19    fn match_count(&self) -> usize;
 20    fn selected_index(&self) -> usize;
 21    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
 22
 23    // fn placeholder_text(&self) -> Arc<str>;
 24    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
 25
 26    fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
 27    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
 28
 29    fn render_match(
 30        &self,
 31        ix: usize,
 32        selected: bool,
 33        cx: &mut ViewContext<Picker<Self>>,
 34    ) -> Self::ListItem;
 35}
 36
 37impl<D: PickerDelegate> Picker<D> {
 38    pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
 39        let editor = cx.build_view(|cx| Editor::single_line(cx));
 40        cx.subscribe(&editor, Self::on_input_editor_event).detach();
 41        Self {
 42            delegate,
 43            scroll_handle: UniformListScrollHandle::new(),
 44            pending_update_matches: None,
 45            editor,
 46        }
 47    }
 48
 49    pub fn focus(&self, cx: &mut WindowContext) {
 50        self.editor.update(cx, |editor, cx| editor.focus(cx));
 51    }
 52
 53    fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
 54        let count = self.delegate.match_count();
 55        if count > 0 {
 56            let index = self.delegate.selected_index();
 57            let ix = cmp::min(index + 1, count - 1);
 58            self.delegate.set_selected_index(ix, cx);
 59            self.scroll_handle.scroll_to_item(ix);
 60        }
 61    }
 62
 63    fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
 64        let count = self.delegate.match_count();
 65        if count > 0 {
 66            let index = self.delegate.selected_index();
 67            let ix = index.saturating_sub(1);
 68            self.delegate.set_selected_index(ix, cx);
 69            self.scroll_handle.scroll_to_item(ix);
 70        }
 71    }
 72
 73    fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
 74        let count = self.delegate.match_count();
 75        if count > 0 {
 76            self.delegate.set_selected_index(0, cx);
 77            self.scroll_handle.scroll_to_item(0);
 78        }
 79    }
 80
 81    fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
 82        let count = self.delegate.match_count();
 83        if count > 0 {
 84            self.delegate.set_selected_index(count - 1, cx);
 85            self.scroll_handle.scroll_to_item(count - 1);
 86        }
 87    }
 88
 89    fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
 90        self.delegate.dismissed(cx);
 91    }
 92
 93    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
 94        self.delegate.confirm(false, cx);
 95    }
 96
 97    fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
 98        self.delegate.confirm(true, cx);
 99    }
100
101    fn on_input_editor_event(
102        &mut self,
103        _: View<Editor>,
104        event: &editor::Event,
105        cx: &mut ViewContext<Self>,
106    ) {
107        if let editor::Event::BufferEdited = event {
108            let query = self.editor.read(cx).text(cx);
109            self.update_matches(query, cx);
110        }
111    }
112
113    pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
114        let update = self.delegate.update_matches(query, cx);
115        self.matches_updated(cx);
116        self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move {
117            update.await;
118            this.update(&mut cx, |this, cx| {
119                this.matches_updated(cx);
120            })
121            .ok()
122        }));
123    }
124
125    fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
126        let index = self.delegate.selected_index();
127        self.scroll_handle.scroll_to_item(index);
128        self.pending_update_matches = None;
129        cx.notify();
130    }
131}
132
133impl<D: PickerDelegate> Render for Picker<D> {
134    type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
135
136    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
137        div()
138            .context("picker")
139            .id("picker-container")
140            .focusable()
141            .size_full()
142            .on_action(Self::select_next)
143            .on_action(Self::select_prev)
144            .on_action(Self::select_first)
145            .on_action(Self::select_last)
146            .on_action(Self::cancel)
147            .on_action(Self::confirm)
148            .on_action(Self::secondary_confirm)
149            .child(self.editor.clone())
150            .child(
151                uniform_list("candidates", self.delegate.match_count(), {
152                    move |this: &mut Self, visible_range, cx| {
153                        let selected_ix = this.delegate.selected_index();
154                        visible_range
155                            .map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
156                            .collect()
157                    }
158                })
159                .track_scroll(self.scroll_handle.clone())
160                .size_full(),
161            )
162    }
163}