1use anyhow::Result;
2use editor::{scroll::Autoscroll, Editor};
3use gpui::{
4 actions, div, impl_actions, list, prelude::*, uniform_list, AnyElement, App, ClickEvent,
5 Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Length,
6 ListSizingBehavior, ListState, MouseButton, MouseUpEvent, Render, ScrollStrategy, Task,
7 UniformListScrollHandle, Window,
8};
9use head::Head;
10use schemars::JsonSchema;
11use serde::Deserialize;
12use std::{sync::Arc, time::Duration};
13use ui::{prelude::*, v_flex, Color, Divider, Label, ListItem, ListItemSpacing};
14use workspace::ModalView;
15
16mod head;
17pub mod highlighted_match_with_paths;
18
19enum ElementContainer {
20 List(ListState),
21 UniformList(UniformListScrollHandle),
22}
23
24actions!(picker, [ConfirmCompletion]);
25
26/// ConfirmInput is an alternative editor action which - instead of selecting active picker entry - treats pickers editor input literally,
27/// performing some kind of action on it.
28#[derive(Clone, PartialEq, Deserialize, JsonSchema, Default)]
29pub struct ConfirmInput {
30 pub secondary: bool,
31}
32
33impl_actions!(picker, [ConfirmInput]);
34
35struct PendingUpdateMatches {
36 delegate_update_matches: Option<Task<()>>,
37 _task: Task<Result<()>>,
38}
39
40pub struct Picker<D: PickerDelegate> {
41 pub delegate: D,
42 element_container: ElementContainer,
43 head: Head,
44 pending_update_matches: Option<PendingUpdateMatches>,
45 confirm_on_update: Option<bool>,
46 width: Option<Length>,
47 max_height: Option<Length>,
48
49 /// Whether the `Picker` is rendered as a self-contained modal.
50 ///
51 /// Set this to `false` when rendering the `Picker` as part of a larger modal.
52 is_modal: bool,
53}
54
55#[derive(Debug, Default, Clone, Copy, PartialEq)]
56pub enum PickerEditorPosition {
57 #[default]
58 /// Render the editor at the start of the picker. Usually the top
59 Start,
60 /// Render the editor at the end of the picker. Usually the bottom
61 End,
62}
63
64pub trait PickerDelegate: Sized + 'static {
65 type ListItem: IntoElement;
66
67 fn match_count(&self) -> usize;
68 fn selected_index(&self) -> usize;
69 fn separators_after_indices(&self) -> Vec<usize> {
70 Vec::new()
71 }
72 fn set_selected_index(
73 &mut self,
74 ix: usize,
75 window: &mut Window,
76 cx: &mut Context<Picker<Self>>,
77 );
78 // Allows binding some optional effect to when the selection changes.
79 fn selected_index_changed(
80 &self,
81 _ix: usize,
82 _window: &mut Window,
83 _cx: &mut Context<Picker<Self>>,
84 ) -> Option<Box<dyn Fn(&mut Window, &mut App) + 'static>> {
85 None
86 }
87 fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str>;
88 fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> SharedString {
89 "No matches".into()
90 }
91 fn update_matches(
92 &mut self,
93 query: String,
94 window: &mut Window,
95 cx: &mut Context<Picker<Self>>,
96 ) -> Task<()>;
97
98 // Delegates that support this method (e.g. the CommandPalette) can chose to block on any background
99 // work for up to `duration` to try and get a result synchronously.
100 // This avoids a flash of an empty command-palette on cmd-shift-p, and lets workspace::SendKeystrokes
101 // mostly work when dismissing a palette.
102 fn finalize_update_matches(
103 &mut self,
104 _query: String,
105 _duration: Duration,
106 _window: &mut Window,
107 _cx: &mut Context<Picker<Self>>,
108 ) -> bool {
109 false
110 }
111
112 /// Override if you want to have <enter> update the query instead of confirming.
113 fn confirm_update_query(
114 &mut self,
115 _window: &mut Window,
116 _cx: &mut Context<Picker<Self>>,
117 ) -> Option<String> {
118 None
119 }
120 fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>);
121 /// Instead of interacting with currently selected entry, treats editor input literally,
122 /// performing some kind of action on it.
123 fn confirm_input(
124 &mut self,
125 _secondary: bool,
126 _window: &mut Window,
127 _: &mut Context<Picker<Self>>,
128 ) {
129 }
130 fn dismissed(&mut self, window: &mut Window, cx: &mut Context<Picker<Self>>);
131 fn should_dismiss(&self) -> bool {
132 true
133 }
134 fn confirm_completion(
135 &mut self,
136 _query: String,
137 _window: &mut Window,
138 _: &mut Context<Picker<Self>>,
139 ) -> Option<String> {
140 None
141 }
142
143 fn editor_position(&self) -> PickerEditorPosition {
144 PickerEditorPosition::default()
145 }
146
147 fn render_editor(
148 &self,
149 editor: &Entity<Editor>,
150 _window: &mut Window,
151 _cx: &mut Context<Picker<Self>>,
152 ) -> Div {
153 v_flex()
154 .when(
155 self.editor_position() == PickerEditorPosition::End,
156 |this| this.child(Divider::horizontal()),
157 )
158 .child(
159 h_flex()
160 .overflow_hidden()
161 .flex_none()
162 .h_9()
163 .px_3()
164 .child(editor.clone()),
165 )
166 .when(
167 self.editor_position() == PickerEditorPosition::Start,
168 |this| this.child(Divider::horizontal()),
169 )
170 }
171
172 fn render_match(
173 &self,
174 ix: usize,
175 selected: bool,
176 window: &mut Window,
177 cx: &mut Context<Picker<Self>>,
178 ) -> Option<Self::ListItem>;
179 fn render_header(
180 &self,
181 _window: &mut Window,
182 _: &mut Context<Picker<Self>>,
183 ) -> Option<AnyElement> {
184 None
185 }
186 fn render_footer(
187 &self,
188 _window: &mut Window,
189 _: &mut Context<Picker<Self>>,
190 ) -> Option<AnyElement> {
191 None
192 }
193}
194
195impl<D: PickerDelegate> Focusable for Picker<D> {
196 fn focus_handle(&self, cx: &App) -> FocusHandle {
197 match &self.head {
198 Head::Editor(editor) => editor.focus_handle(cx),
199 Head::Empty(head) => head.focus_handle(cx),
200 }
201 }
202}
203
204#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
205enum ContainerKind {
206 List,
207 UniformList,
208}
209
210impl<D: PickerDelegate> Picker<D> {
211 /// A picker, which displays its matches using `gpui::uniform_list`, all matches should have the same height.
212 /// The picker allows the user to perform search items by text.
213 /// If `PickerDelegate::render_match` can return items with different heights, use `Picker::list`.
214 pub fn uniform_list(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self {
215 let head = Head::editor(
216 delegate.placeholder_text(window, cx),
217 Self::on_input_editor_event,
218 window,
219 cx,
220 );
221
222 Self::new(delegate, ContainerKind::UniformList, head, window, cx)
223 }
224
225 /// A picker, which displays its matches using `gpui::uniform_list`, all matches should have the same height.
226 /// If `PickerDelegate::render_match` can return items with different heights, use `Picker::list`.
227 pub fn nonsearchable_uniform_list(
228 delegate: D,
229 window: &mut Window,
230 cx: &mut Context<Self>,
231 ) -> Self {
232 let head = Head::empty(Self::on_empty_head_blur, window, cx);
233
234 Self::new(delegate, ContainerKind::UniformList, head, window, cx)
235 }
236
237 /// A picker, which displays its matches using `gpui::list`, matches can have different heights.
238 /// The picker allows the user to perform search items by text.
239 /// If `PickerDelegate::render_match` only returns items with the same height, use `Picker::uniform_list` as its implementation is optimized for that.
240 pub fn list(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self {
241 let head = Head::editor(
242 delegate.placeholder_text(window, cx),
243 Self::on_input_editor_event,
244 window,
245 cx,
246 );
247
248 Self::new(delegate, ContainerKind::List, head, window, cx)
249 }
250
251 fn new(
252 delegate: D,
253 container: ContainerKind,
254 head: Head,
255 window: &mut Window,
256 cx: &mut Context<Self>,
257 ) -> Self {
258 let mut this = Self {
259 delegate,
260 head,
261 element_container: Self::create_element_container(container, cx),
262 pending_update_matches: None,
263 confirm_on_update: None,
264 width: None,
265 max_height: Some(rems(18.).into()),
266 is_modal: true,
267 };
268 this.update_matches("".to_string(), window, cx);
269 // give the delegate 4ms to render the first set of suggestions.
270 this.delegate
271 .finalize_update_matches("".to_string(), Duration::from_millis(4), window, cx);
272 this
273 }
274
275 fn create_element_container(
276 container: ContainerKind,
277 cx: &mut Context<Self>,
278 ) -> ElementContainer {
279 match container {
280 ContainerKind::UniformList => {
281 ElementContainer::UniformList(UniformListScrollHandle::new())
282 }
283 ContainerKind::List => {
284 let model = cx.entity().downgrade();
285 ElementContainer::List(ListState::new(
286 0,
287 gpui::ListAlignment::Top,
288 px(1000.),
289 move |ix, window, cx| {
290 model
291 .upgrade()
292 .map(|model| {
293 model.update(cx, |this, cx| {
294 this.render_element(window, cx, ix).into_any_element()
295 })
296 })
297 .unwrap_or_else(|| div().into_any_element())
298 },
299 ))
300 }
301 }
302 }
303
304 pub fn width(mut self, width: impl Into<gpui::Length>) -> Self {
305 self.width = Some(width.into());
306 self
307 }
308
309 pub fn max_height(mut self, max_height: Option<gpui::Length>) -> Self {
310 self.max_height = max_height;
311 self
312 }
313
314 pub fn modal(mut self, modal: bool) -> Self {
315 self.is_modal = modal;
316 self
317 }
318
319 pub fn focus(&self, window: &mut Window, cx: &mut App) {
320 self.focus_handle(cx).focus(window);
321 }
322
323 /// Handles the selecting an index, and passing the change to the delegate.
324 /// If `scroll_to_index` is true, the new selected index will be scrolled into view.
325 ///
326 /// If some effect is bound to `selected_index_changed`, it will be executed.
327 pub fn set_selected_index(
328 &mut self,
329 ix: usize,
330 scroll_to_index: bool,
331 window: &mut Window,
332 cx: &mut Context<Self>,
333 ) {
334 let previous_index = self.delegate.selected_index();
335 self.delegate.set_selected_index(ix, window, cx);
336 let current_index = self.delegate.selected_index();
337
338 if previous_index != current_index {
339 if let Some(action) = self.delegate.selected_index_changed(ix, window, cx) {
340 action(window, cx);
341 }
342 if scroll_to_index {
343 self.scroll_to_item_index(ix);
344 }
345 }
346 }
347
348 pub fn select_next(
349 &mut self,
350 _: &menu::SelectNext,
351 window: &mut Window,
352 cx: &mut Context<Self>,
353 ) {
354 let count = self.delegate.match_count();
355 if count > 0 {
356 let index = self.delegate.selected_index();
357 let ix = if index == count - 1 { 0 } else { index + 1 };
358 self.set_selected_index(ix, true, window, cx);
359 cx.notify();
360 }
361 }
362
363 fn select_prev(&mut self, _: &menu::SelectPrev, window: &mut Window, cx: &mut Context<Self>) {
364 let count = self.delegate.match_count();
365 if count > 0 {
366 let index = self.delegate.selected_index();
367 let ix = if index == 0 { count - 1 } else { index - 1 };
368 self.set_selected_index(ix, true, window, cx);
369 cx.notify();
370 }
371 }
372
373 fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
374 let count = self.delegate.match_count();
375 if count > 0 {
376 self.set_selected_index(0, true, window, cx);
377 cx.notify();
378 }
379 }
380
381 fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
382 let count = self.delegate.match_count();
383 if count > 0 {
384 self.set_selected_index(count - 1, true, window, cx);
385 cx.notify();
386 }
387 }
388
389 pub fn cycle_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
390 let count = self.delegate.match_count();
391 let index = self.delegate.selected_index();
392 let new_index = if index + 1 == count { 0 } else { index + 1 };
393 self.set_selected_index(new_index, true, window, cx);
394 cx.notify();
395 }
396
397 pub fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
398 if self.delegate.should_dismiss() {
399 self.delegate.dismissed(window, cx);
400 cx.emit(DismissEvent);
401 }
402 }
403
404 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
405 if self.pending_update_matches.is_some()
406 && !self.delegate.finalize_update_matches(
407 self.query(cx),
408 Duration::from_millis(16),
409 window,
410 cx,
411 )
412 {
413 self.confirm_on_update = Some(false)
414 } else {
415 self.pending_update_matches.take();
416 self.do_confirm(false, window, cx);
417 }
418 }
419
420 fn secondary_confirm(
421 &mut self,
422 _: &menu::SecondaryConfirm,
423 window: &mut Window,
424 cx: &mut Context<Self>,
425 ) {
426 if self.pending_update_matches.is_some()
427 && !self.delegate.finalize_update_matches(
428 self.query(cx),
429 Duration::from_millis(16),
430 window,
431 cx,
432 )
433 {
434 self.confirm_on_update = Some(true)
435 } else {
436 self.do_confirm(true, window, cx);
437 }
438 }
439
440 fn confirm_input(&mut self, input: &ConfirmInput, window: &mut Window, cx: &mut Context<Self>) {
441 self.delegate.confirm_input(input.secondary, window, cx);
442 }
443
444 fn confirm_completion(
445 &mut self,
446 _: &ConfirmCompletion,
447 window: &mut Window,
448 cx: &mut Context<Self>,
449 ) {
450 if let Some(new_query) = self.delegate.confirm_completion(self.query(cx), window, cx) {
451 self.set_query(new_query, window, cx);
452 } else {
453 cx.propagate()
454 }
455 }
456
457 fn handle_click(
458 &mut self,
459 ix: usize,
460 secondary: bool,
461 window: &mut Window,
462 cx: &mut Context<Self>,
463 ) {
464 cx.stop_propagation();
465 window.prevent_default();
466 self.set_selected_index(ix, false, window, cx);
467 self.do_confirm(secondary, window, cx)
468 }
469
470 fn do_confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Self>) {
471 if let Some(update_query) = self.delegate.confirm_update_query(window, cx) {
472 self.set_query(update_query, window, cx);
473 self.delegate.set_selected_index(0, window, cx);
474 } else {
475 self.delegate.confirm(secondary, window, cx)
476 }
477 }
478
479 fn on_input_editor_event(
480 &mut self,
481 _: &Entity<Editor>,
482 event: &editor::EditorEvent,
483 window: &mut Window,
484 cx: &mut Context<Self>,
485 ) {
486 let Head::Editor(ref editor) = &self.head else {
487 panic!("unexpected call");
488 };
489 match event {
490 editor::EditorEvent::BufferEdited => {
491 let query = editor.read(cx).text(cx);
492 self.update_matches(query, window, cx);
493 }
494 editor::EditorEvent::Blurred => {
495 self.cancel(&menu::Cancel, window, cx);
496 }
497 _ => {}
498 }
499 }
500
501 fn on_empty_head_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
502 let Head::Empty(_) = &self.head else {
503 panic!("unexpected call");
504 };
505 self.cancel(&menu::Cancel, window, cx);
506 }
507
508 pub fn refresh_placeholder(&mut self, window: &mut Window, cx: &mut App) {
509 match &self.head {
510 Head::Editor(editor) => {
511 let placeholder = self.delegate.placeholder_text(window, cx);
512 editor.update(cx, |editor, cx| {
513 editor.set_placeholder_text(placeholder, cx);
514 cx.notify();
515 });
516 }
517 Head::Empty(_) => {}
518 }
519 }
520
521 pub fn refresh(&mut self, window: &mut Window, cx: &mut Context<Self>) {
522 let query = self.query(cx);
523 self.update_matches(query, window, cx);
524 }
525
526 pub fn update_matches(&mut self, query: String, window: &mut Window, cx: &mut Context<Self>) {
527 let delegate_pending_update_matches = self.delegate.update_matches(query, window, cx);
528
529 self.matches_updated(window, cx);
530 // This struct ensures that we can synchronously drop the task returned by the
531 // delegate's `update_matches` method and the task that the picker is spawning.
532 // If we simply capture the delegate's task into the picker's task, when the picker's
533 // task gets synchronously dropped, the delegate's task would keep running until
534 // the picker's task has a chance of being scheduled, because dropping a task happens
535 // asynchronously.
536 self.pending_update_matches = Some(PendingUpdateMatches {
537 delegate_update_matches: Some(delegate_pending_update_matches),
538 _task: cx.spawn_in(window, |this, mut cx| async move {
539 let delegate_pending_update_matches = this.update(&mut cx, |this, _| {
540 this.pending_update_matches
541 .as_mut()
542 .unwrap()
543 .delegate_update_matches
544 .take()
545 .unwrap()
546 })?;
547 delegate_pending_update_matches.await;
548 this.update_in(&mut cx, |this, window, cx| {
549 this.matches_updated(window, cx);
550 })
551 }),
552 });
553 }
554
555 fn matches_updated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
556 if let ElementContainer::List(state) = &mut self.element_container {
557 state.reset(self.delegate.match_count());
558 }
559
560 let index = self.delegate.selected_index();
561 self.scroll_to_item_index(index);
562 self.pending_update_matches = None;
563 if let Some(secondary) = self.confirm_on_update.take() {
564 self.do_confirm(secondary, window, cx);
565 }
566 cx.notify();
567 }
568
569 pub fn query(&self, cx: &App) -> String {
570 match &self.head {
571 Head::Editor(editor) => editor.read(cx).text(cx),
572 Head::Empty(_) => "".to_string(),
573 }
574 }
575
576 pub fn set_query(&self, query: impl Into<Arc<str>>, window: &mut Window, cx: &mut App) {
577 if let Head::Editor(ref editor) = &self.head {
578 editor.update(cx, |editor, cx| {
579 editor.set_text(query, window, cx);
580 let editor_offset = editor.buffer().read(cx).len(cx);
581 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
582 s.select_ranges(Some(editor_offset..editor_offset))
583 });
584 });
585 }
586 }
587
588 fn scroll_to_item_index(&mut self, ix: usize) {
589 match &mut self.element_container {
590 ElementContainer::List(state) => state.scroll_to_reveal_item(ix),
591 ElementContainer::UniformList(scroll_handle) => {
592 scroll_handle.scroll_to_item(ix, ScrollStrategy::Top)
593 }
594 }
595 }
596
597 fn render_element(
598 &self,
599 window: &mut Window,
600 cx: &mut Context<Self>,
601 ix: usize,
602 ) -> impl IntoElement {
603 div()
604 .id(("item", ix))
605 .cursor_pointer()
606 .on_click(cx.listener(move |this, event: &ClickEvent, window, cx| {
607 this.handle_click(ix, event.modifiers().secondary(), window, cx)
608 }))
609 // As of this writing, GPUI intercepts `ctrl-[mouse-event]`s on macOS
610 // and produces right mouse button events. This matches platforms norms
611 // but means that UIs which depend on holding ctrl down (such as the tab
612 // switcher) can't be clicked on. Hence, this handler.
613 .on_mouse_up(
614 MouseButton::Right,
615 cx.listener(move |this, event: &MouseUpEvent, window, cx| {
616 // We specifically want to use the platform key here, as
617 // ctrl will already be held down for the tab switcher.
618 this.handle_click(ix, event.modifiers.platform, window, cx)
619 }),
620 )
621 .children(self.delegate.render_match(
622 ix,
623 ix == self.delegate.selected_index(),
624 window,
625 cx,
626 ))
627 .when(
628 self.delegate.separators_after_indices().contains(&ix),
629 |picker| {
630 picker
631 .border_color(cx.theme().colors().border_variant)
632 .border_b_1()
633 .py(px(-1.0))
634 },
635 )
636 }
637
638 fn render_element_container(&self, cx: &mut Context<Self>) -> impl IntoElement {
639 let sizing_behavior = if self.max_height.is_some() {
640 ListSizingBehavior::Infer
641 } else {
642 ListSizingBehavior::Auto
643 };
644 match &self.element_container {
645 ElementContainer::UniformList(scroll_handle) => uniform_list(
646 cx.entity().clone(),
647 "candidates",
648 self.delegate.match_count(),
649 move |picker, visible_range, window, cx| {
650 visible_range
651 .map(|ix| picker.render_element(window, cx, ix))
652 .collect()
653 },
654 )
655 .with_sizing_behavior(sizing_behavior)
656 .flex_grow()
657 .py_1()
658 .track_scroll(scroll_handle.clone())
659 .into_any_element(),
660 ElementContainer::List(state) => list(state.clone())
661 .with_sizing_behavior(sizing_behavior)
662 .flex_grow()
663 .py_2()
664 .into_any_element(),
665 }
666 }
667
668 #[cfg(any(test, feature = "test-support"))]
669 pub fn logical_scroll_top_index(&self) -> usize {
670 match &self.element_container {
671 ElementContainer::List(state) => state.logical_scroll_top().item_ix,
672 ElementContainer::UniformList(scroll_handle) => {
673 scroll_handle.logical_scroll_top_index()
674 }
675 }
676 }
677}
678
679impl<D: PickerDelegate> EventEmitter<DismissEvent> for Picker<D> {}
680impl<D: PickerDelegate> ModalView for Picker<D> {}
681
682impl<D: PickerDelegate> Render for Picker<D> {
683 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
684 let editor_position = self.delegate.editor_position();
685
686 v_flex()
687 .key_context("Picker")
688 .size_full()
689 .when_some(self.width, |el, width| el.w(width))
690 .overflow_hidden()
691 // This is a bit of a hack to remove the modal styling when we're rendering the `Picker`
692 // as a part of a modal rather than the entire modal.
693 //
694 // We should revisit how the `Picker` is styled to make it more composable.
695 .when(self.is_modal, |this| this.elevation_3(cx))
696 .on_action(cx.listener(Self::select_next))
697 .on_action(cx.listener(Self::select_prev))
698 .on_action(cx.listener(Self::select_first))
699 .on_action(cx.listener(Self::select_last))
700 .on_action(cx.listener(Self::cancel))
701 .on_action(cx.listener(Self::confirm))
702 .on_action(cx.listener(Self::secondary_confirm))
703 .on_action(cx.listener(Self::confirm_completion))
704 .on_action(cx.listener(Self::confirm_input))
705 .children(match &self.head {
706 Head::Editor(editor) => {
707 if editor_position == PickerEditorPosition::Start {
708 Some(self.delegate.render_editor(&editor.clone(), window, cx))
709 } else {
710 None
711 }
712 }
713 Head::Empty(empty_head) => Some(div().child(empty_head.clone())),
714 })
715 .when(self.delegate.match_count() > 0, |el| {
716 el.child(
717 v_flex()
718 .flex_grow()
719 .when_some(self.max_height, |div, max_h| div.max_h(max_h))
720 .overflow_hidden()
721 .children(self.delegate.render_header(window, cx))
722 .child(self.render_element_container(cx)),
723 )
724 })
725 .when(self.delegate.match_count() == 0, |el| {
726 el.child(
727 v_flex().flex_grow().py_2().child(
728 ListItem::new("empty_state")
729 .inset(true)
730 .spacing(ListItemSpacing::Sparse)
731 .disabled(true)
732 .child(
733 Label::new(self.delegate.no_matches_text(window, cx))
734 .color(Color::Muted),
735 ),
736 ),
737 )
738 })
739 .children(self.delegate.render_footer(window, cx))
740 .children(match &self.head {
741 Head::Editor(editor) => {
742 if editor_position == PickerEditorPosition::End {
743 Some(self.delegate.render_editor(&editor.clone(), window, cx))
744 } else {
745 None
746 }
747 }
748 Head::Empty(empty_head) => Some(div().child(empty_head.clone())),
749 })
750 }
751}