1use editor::Editor;
2use gpui::{
3 elements::{
4 ChildView, Flex, Label, MouseEventHandler, ParentElement, ScrollTarget, UniformList,
5 UniformListState,
6 },
7 geometry::vector::{vec2f, Vector2F},
8 keymap,
9 platform::CursorStyle,
10 AnyViewHandle, AppContext, Axis, Element, ElementBox, Entity, MouseButton, MouseState,
11 MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
12};
13use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev};
14use settings::Settings;
15use std::cmp;
16
17pub struct Picker<D: PickerDelegate> {
18 delegate: WeakViewHandle<D>,
19 query_editor: ViewHandle<Editor>,
20 list_state: UniformListState,
21 max_size: Vector2F,
22 theme: Box<dyn FnMut(&AppContext) -> &theme::Picker>,
23 confirmed: bool,
24}
25
26pub trait PickerDelegate: View {
27 fn match_count(&self) -> usize;
28 fn selected_index(&self) -> usize;
29 fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>);
30 fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()>;
31 fn confirm(&mut self, cx: &mut ViewContext<Self>);
32 fn dismiss(&mut self, cx: &mut ViewContext<Self>);
33 fn render_match(
34 &self,
35 ix: usize,
36 state: &mut MouseState,
37 selected: bool,
38 cx: &AppContext,
39 ) -> ElementBox;
40 fn center_selection_after_match_updates(&self) -> bool {
41 false
42 }
43}
44
45impl<D: PickerDelegate> Entity for Picker<D> {
46 type Event = ();
47}
48
49impl<D: PickerDelegate> View for Picker<D> {
50 fn ui_name() -> &'static str {
51 "Picker"
52 }
53
54 fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
55 let theme = (self.theme)(cx);
56 let container_style = theme.container;
57 let delegate = self.delegate.clone();
58 let match_count = if let Some(delegate) = delegate.upgrade(cx.app) {
59 delegate.read(cx).match_count()
60 } else {
61 0
62 };
63
64 Flex::new(Axis::Vertical)
65 .with_child(
66 ChildView::new(&self.query_editor, cx)
67 .contained()
68 .with_style(theme.input_editor.container)
69 .boxed(),
70 )
71 .with_child(
72 if match_count == 0 {
73 Label::new("No matches".into(), theme.empty.label.clone())
74 .contained()
75 .with_style(theme.empty.container)
76 } else {
77 UniformList::new(
78 self.list_state.clone(),
79 match_count,
80 cx,
81 move |this, mut range, items, cx| {
82 let delegate = this.delegate.upgrade(cx).unwrap();
83 let selected_ix = delegate.read(cx).selected_index();
84 range.end = cmp::min(range.end, delegate.read(cx).match_count());
85 items.extend(range.map(move |ix| {
86 MouseEventHandler::<D>::new(ix, cx, |state, cx| {
87 delegate
88 .read(cx)
89 .render_match(ix, state, ix == selected_ix, cx)
90 })
91 .on_down(MouseButton::Left, move |_, cx| {
92 cx.dispatch_action(SelectIndex(ix))
93 })
94 .with_cursor_style(CursorStyle::PointingHand)
95 .boxed()
96 }));
97 },
98 )
99 .contained()
100 .with_margin_top(6.0)
101 }
102 .flex(1., false)
103 .boxed(),
104 )
105 .contained()
106 .with_style(container_style)
107 .constrained()
108 .with_max_width(self.max_size.x())
109 .with_max_height(self.max_size.y())
110 .named("picker")
111 }
112
113 fn keymap_context(&self, _: &AppContext) -> keymap::Context {
114 let mut cx = Self::default_keymap_context();
115 cx.set.insert("menu".into());
116 cx
117 }
118
119 fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
120 if cx.is_self_focused() {
121 cx.focus(&self.query_editor);
122 }
123 }
124}
125
126impl<D: PickerDelegate> Picker<D> {
127 pub fn init(cx: &mut MutableAppContext) {
128 cx.add_action(Self::select_first);
129 cx.add_action(Self::select_last);
130 cx.add_action(Self::select_next);
131 cx.add_action(Self::select_prev);
132 cx.add_action(Self::select_index);
133 cx.add_action(Self::confirm);
134 cx.add_action(Self::cancel);
135 }
136
137 pub fn new(delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self {
138 let query_editor = cx.add_view(|cx| {
139 Editor::single_line(Some(|theme| theme.picker.input_editor.clone()), cx)
140 });
141 cx.subscribe(&query_editor, Self::on_query_editor_event)
142 .detach();
143 let this = Self {
144 query_editor,
145 list_state: Default::default(),
146 delegate,
147 max_size: vec2f(540., 420.),
148 theme: Box::new(|cx| &cx.global::<Settings>().theme.picker),
149 confirmed: false,
150 };
151 cx.defer(|this, cx| {
152 if let Some(delegate) = this.delegate.upgrade(cx) {
153 cx.observe(&delegate, |_, _, cx| cx.notify()).detach();
154 this.update_matches(String::new(), cx)
155 }
156 });
157 this
158 }
159
160 pub fn with_max_size(mut self, width: f32, height: f32) -> Self {
161 self.max_size = vec2f(width, height);
162 self
163 }
164
165 pub fn with_theme<F>(mut self, theme: F) -> Self
166 where
167 F: 'static + FnMut(&AppContext) -> &theme::Picker,
168 {
169 self.theme = Box::new(theme);
170 self
171 }
172
173 pub fn query(&self, cx: &AppContext) -> String {
174 self.query_editor.read(cx).text(cx)
175 }
176
177 fn on_query_editor_event(
178 &mut self,
179 _: ViewHandle<Editor>,
180 event: &editor::Event,
181 cx: &mut ViewContext<Self>,
182 ) {
183 match event {
184 editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx),
185 editor::Event::Blurred if !self.confirmed => {
186 if let Some(delegate) = self.delegate.upgrade(cx) {
187 delegate.update(cx, |delegate, cx| {
188 delegate.dismiss(cx);
189 })
190 }
191 }
192 _ => {}
193 }
194 }
195
196 pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
197 if let Some(delegate) = self.delegate.upgrade(cx) {
198 let update = delegate.update(cx, |d, cx| d.update_matches(query, cx));
199 cx.spawn(|this, mut cx| async move {
200 update.await;
201 this.update(&mut cx, |this, cx| {
202 if let Some(delegate) = this.delegate.upgrade(cx) {
203 let delegate = delegate.read(cx);
204 let index = delegate.selected_index();
205 let target = if delegate.center_selection_after_match_updates() {
206 ScrollTarget::Center(index)
207 } else {
208 ScrollTarget::Show(index)
209 };
210 this.list_state.scroll_to(target);
211 cx.notify();
212 }
213 });
214 })
215 .detach()
216 }
217 }
218
219 pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
220 if let Some(delegate) = self.delegate.upgrade(cx) {
221 let index = 0;
222 delegate.update(cx, |delegate, cx| delegate.set_selected_index(0, cx));
223 self.list_state.scroll_to(ScrollTarget::Show(index));
224 cx.notify();
225 }
226 }
227
228 pub fn select_index(&mut self, action: &SelectIndex, cx: &mut ViewContext<Self>) {
229 if let Some(delegate) = self.delegate.upgrade(cx) {
230 let index = action.0;
231 self.confirmed = true;
232 delegate.update(cx, |delegate, cx| {
233 delegate.set_selected_index(index, cx);
234 delegate.confirm(cx);
235 });
236 }
237 }
238
239 pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
240 if let Some(delegate) = self.delegate.upgrade(cx) {
241 let index = delegate.update(cx, |delegate, cx| {
242 let match_count = delegate.match_count();
243 let index = if match_count > 0 { match_count - 1 } else { 0 };
244 delegate.set_selected_index(index, cx);
245 index
246 });
247 self.list_state.scroll_to(ScrollTarget::Show(index));
248 cx.notify();
249 }
250 }
251
252 pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
253 if let Some(delegate) = self.delegate.upgrade(cx) {
254 let index = delegate.update(cx, |delegate, cx| {
255 let mut selected_index = delegate.selected_index();
256 if selected_index + 1 < delegate.match_count() {
257 selected_index += 1;
258 delegate.set_selected_index(selected_index, cx);
259 }
260 selected_index
261 });
262 self.list_state.scroll_to(ScrollTarget::Show(index));
263 cx.notify();
264 }
265 }
266
267 pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
268 if let Some(delegate) = self.delegate.upgrade(cx) {
269 let index = delegate.update(cx, |delegate, cx| {
270 let mut selected_index = delegate.selected_index();
271 if selected_index > 0 {
272 selected_index -= 1;
273 delegate.set_selected_index(selected_index, cx);
274 }
275 selected_index
276 });
277 self.list_state.scroll_to(ScrollTarget::Show(index));
278 cx.notify();
279 }
280 }
281
282 fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
283 if let Some(delegate) = self.delegate.upgrade(cx) {
284 self.confirmed = true;
285 delegate.update(cx, |delegate, cx| delegate.confirm(cx));
286 }
287 }
288
289 fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
290 if let Some(delegate) = self.delegate.upgrade(cx) {
291 delegate.update(cx, |delegate, cx| delegate.dismiss(cx));
292 }
293 }
294}