1use editor::Editor;
2use gpui::{
3 div, uniform_list, Component, Div, FocusEnabled, ParentElement, Render, StatefulInteractivity,
4 StatelessInteractive, Styled, 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}
14
15pub trait PickerDelegate: Sized + 'static {
16 type ListItem: Component<Picker<Self>>;
17
18 fn match_count(&self) -> usize;
19 fn selected_index(&self) -> usize;
20 fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
21
22 // fn placeholder_text(&self) -> Arc<str>;
23 // fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
24
25 fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
26 fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
27
28 fn render_match(
29 &self,
30 ix: usize,
31 selected: bool,
32 cx: &mut ViewContext<Picker<Self>>,
33 ) -> Self::ListItem;
34}
35
36impl<D: PickerDelegate> Picker<D> {
37 pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
38 Self {
39 delegate,
40 scroll_handle: UniformListScrollHandle::new(),
41 editor: cx.build_view(|cx| Editor::single_line(cx)),
42 }
43 }
44
45 pub fn focus(&self, cx: &mut WindowContext) {
46 self.editor.update(cx, |editor, cx| editor.focus(cx));
47 }
48
49 fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
50 let count = self.delegate.match_count();
51 if count > 0 {
52 let index = self.delegate.selected_index();
53 let ix = cmp::min(index + 1, count - 1);
54 self.delegate.set_selected_index(ix, cx);
55 self.scroll_handle.scroll_to_item(ix);
56 }
57 }
58
59 fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
60 let count = self.delegate.match_count();
61 if count > 0 {
62 let index = self.delegate.selected_index();
63 let ix = index.saturating_sub(1);
64 self.delegate.set_selected_index(ix, cx);
65 self.scroll_handle.scroll_to_item(ix);
66 }
67 }
68
69 fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
70 let count = self.delegate.match_count();
71 if count > 0 {
72 self.delegate.set_selected_index(0, cx);
73 self.scroll_handle.scroll_to_item(0);
74 }
75 }
76
77 fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
78 let count = self.delegate.match_count();
79 if count > 0 {
80 self.delegate.set_selected_index(count - 1, cx);
81 self.scroll_handle.scroll_to_item(count - 1);
82 }
83 }
84
85 fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
86 self.delegate.dismissed(cx);
87 }
88
89 fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
90 self.delegate.confirm(false, cx);
91 }
92
93 fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
94 self.delegate.confirm(true, cx);
95 }
96}
97
98impl<D: PickerDelegate> Render for Picker<D> {
99 type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
100
101 fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
102 div()
103 .context("picker")
104 .id("picker-container")
105 .focusable()
106 .size_full()
107 .on_action(Self::select_next)
108 .on_action(Self::select_prev)
109 .on_action(Self::select_first)
110 .on_action(Self::select_last)
111 .on_action(Self::cancel)
112 .on_action(Self::confirm)
113 .on_action(Self::secondary_confirm)
114 .child(self.editor.clone())
115 .child(
116 uniform_list("candidates", self.delegate.match_count(), {
117 move |this: &mut Self, visible_range, cx| {
118 let selected_ix = this.delegate.selected_index();
119 visible_range
120 .map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
121 .collect()
122 }
123 })
124 .track_scroll(self.scroll_handle.clone())
125 .size_full(),
126 )
127 }
128}