picker.rs

   1mod head;
   2pub mod highlighted_match_with_paths;
   3pub mod popover_menu;
   4
   5use anyhow::Result;
   6use editor::{
   7    Editor, SelectionEffects,
   8    actions::{MoveDown, MoveUp},
   9    scroll::Autoscroll,
  10};
  11use gpui::{
  12    Action, AnyElement, App, Bounds, ClickEvent, Context, DismissEvent, Entity, EventEmitter,
  13    FocusHandle, Focusable, Length, ListSizingBehavior, ListState, MouseButton, MouseUpEvent,
  14    Pixels, Render, ScrollStrategy, Task, UniformListScrollHandle, Window, actions, canvas, div,
  15    list, prelude::*, uniform_list,
  16};
  17use head::Head;
  18use schemars::JsonSchema;
  19use serde::Deserialize;
  20use std::{
  21    cell::Cell, cell::RefCell, collections::HashMap, ops::Range, rc::Rc, sync::Arc, time::Duration,
  22};
  23use theme::ThemeSettings;
  24use ui::{
  25    Color, Divider, DocumentationAside, DocumentationSide, Label, ListItem, ListItemSpacing,
  26    ScrollAxes, Scrollbars, WithScrollbar, prelude::*, utils::WithRemSize, v_flex,
  27};
  28use workspace::{ModalView, item::Settings};
  29
  30enum ElementContainer {
  31    List(ListState),
  32    UniformList(UniformListScrollHandle),
  33}
  34
  35pub enum Direction {
  36    Up,
  37    Down,
  38}
  39
  40actions!(
  41    picker,
  42    [
  43        /// Confirms the selected completion in the picker.
  44        ConfirmCompletion
  45    ]
  46);
  47
  48/// ConfirmInput is an alternative editor action which - instead of selecting active picker entry - treats pickers editor input literally,
  49/// performing some kind of action on it.
  50#[derive(Clone, PartialEq, Deserialize, JsonSchema, Default, Action)]
  51#[action(namespace = picker)]
  52#[serde(deny_unknown_fields)]
  53pub struct ConfirmInput {
  54    pub secondary: bool,
  55}
  56
  57struct PendingUpdateMatches {
  58    delegate_update_matches: Option<Task<()>>,
  59    _task: Task<Result<()>>,
  60}
  61
  62pub struct Picker<D: PickerDelegate> {
  63    pub delegate: D,
  64    element_container: ElementContainer,
  65    head: Head,
  66    pending_update_matches: Option<PendingUpdateMatches>,
  67    confirm_on_update: Option<bool>,
  68    width: Option<Length>,
  69    widest_item: Option<usize>,
  70    max_height: Option<Length>,
  71    /// An external control to display a scrollbar in the `Picker`.
  72    show_scrollbar: bool,
  73    /// Whether the `Picker` is rendered as a self-contained modal.
  74    ///
  75    /// Set this to `false` when rendering the `Picker` as part of a larger modal.
  76    is_modal: bool,
  77    /// Bounds tracking for the picker container (for aside positioning)
  78    picker_bounds: Rc<Cell<Option<Bounds<Pixels>>>>,
  79    /// Bounds tracking for items (for aside positioning) - maps item index to bounds
  80    item_bounds: Rc<RefCell<HashMap<usize, Bounds<Pixels>>>>,
  81}
  82
  83#[derive(Debug, Default, Clone, Copy, PartialEq)]
  84pub enum PickerEditorPosition {
  85    #[default]
  86    /// Render the editor at the start of the picker. Usually the top
  87    Start,
  88    /// Render the editor at the end of the picker. Usually the bottom
  89    End,
  90}
  91
  92pub trait PickerDelegate: Sized + 'static {
  93    type ListItem: IntoElement;
  94
  95    fn match_count(&self) -> usize;
  96    fn selected_index(&self) -> usize;
  97    fn separators_after_indices(&self) -> Vec<usize> {
  98        Vec::new()
  99    }
 100    fn set_selected_index(
 101        &mut self,
 102        ix: usize,
 103        window: &mut Window,
 104        cx: &mut Context<Picker<Self>>,
 105    );
 106
 107    /// Called before the picker handles `SelectPrevious` or `SelectNext`. Return `Some(query)` to
 108    /// set a new query and prevent the default selection behavior.
 109    fn select_history(
 110        &mut self,
 111        _direction: Direction,
 112        _query: &str,
 113        _window: &mut Window,
 114        _cx: &mut App,
 115    ) -> Option<String> {
 116        None
 117    }
 118    fn can_select(
 119        &mut self,
 120        _ix: usize,
 121        _window: &mut Window,
 122        _cx: &mut Context<Picker<Self>>,
 123    ) -> bool {
 124        true
 125    }
 126
 127    // Allows binding some optional effect to when the selection changes.
 128    fn selected_index_changed(
 129        &self,
 130        _ix: usize,
 131        _window: &mut Window,
 132        _cx: &mut Context<Picker<Self>>,
 133    ) -> Option<Box<dyn Fn(&mut Window, &mut App) + 'static>> {
 134        None
 135    }
 136    fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str>;
 137    fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> {
 138        Some("No matches".into())
 139    }
 140    fn update_matches(
 141        &mut self,
 142        query: String,
 143        window: &mut Window,
 144        cx: &mut Context<Picker<Self>>,
 145    ) -> Task<()>;
 146
 147    // Delegates that support this method (e.g. the CommandPalette) can chose to block on any background
 148    // work for up to `duration` to try and get a result synchronously.
 149    // This avoids a flash of an empty command-palette on cmd-shift-p, and lets workspace::SendKeystrokes
 150    // mostly work when dismissing a palette.
 151    fn finalize_update_matches(
 152        &mut self,
 153        _query: String,
 154        _duration: Duration,
 155        _window: &mut Window,
 156        _cx: &mut Context<Picker<Self>>,
 157    ) -> bool {
 158        false
 159    }
 160
 161    /// Override if you want to have <enter> update the query instead of confirming.
 162    fn confirm_update_query(
 163        &mut self,
 164        _window: &mut Window,
 165        _cx: &mut Context<Picker<Self>>,
 166    ) -> Option<String> {
 167        None
 168    }
 169    fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>);
 170    /// Instead of interacting with currently selected entry, treats editor input literally,
 171    /// performing some kind of action on it.
 172    fn confirm_input(
 173        &mut self,
 174        _secondary: bool,
 175        _window: &mut Window,
 176        _: &mut Context<Picker<Self>>,
 177    ) {
 178    }
 179    fn dismissed(&mut self, window: &mut Window, cx: &mut Context<Picker<Self>>);
 180    fn should_dismiss(&self) -> bool {
 181        true
 182    }
 183    fn confirm_completion(
 184        &mut self,
 185        _query: String,
 186        _window: &mut Window,
 187        _: &mut Context<Picker<Self>>,
 188    ) -> Option<String> {
 189        None
 190    }
 191
 192    fn editor_position(&self) -> PickerEditorPosition {
 193        PickerEditorPosition::default()
 194    }
 195
 196    fn render_editor(
 197        &self,
 198        editor: &Entity<Editor>,
 199        _window: &mut Window,
 200        _cx: &mut Context<Picker<Self>>,
 201    ) -> Div {
 202        v_flex()
 203            .when(
 204                self.editor_position() == PickerEditorPosition::End,
 205                |this| this.child(Divider::horizontal()),
 206            )
 207            .child(
 208                h_flex()
 209                    .overflow_hidden()
 210                    .flex_none()
 211                    .h_9()
 212                    .px_2p5()
 213                    .child(editor.clone()),
 214            )
 215            .when(
 216                self.editor_position() == PickerEditorPosition::Start,
 217                |this| this.child(Divider::horizontal()),
 218            )
 219    }
 220
 221    fn render_match(
 222        &self,
 223        ix: usize,
 224        selected: bool,
 225        window: &mut Window,
 226        cx: &mut Context<Picker<Self>>,
 227    ) -> Option<Self::ListItem>;
 228
 229    fn render_header(
 230        &self,
 231        _window: &mut Window,
 232        _: &mut Context<Picker<Self>>,
 233    ) -> Option<AnyElement> {
 234        None
 235    }
 236
 237    fn render_footer(
 238        &self,
 239        _window: &mut Window,
 240        _: &mut Context<Picker<Self>>,
 241    ) -> Option<AnyElement> {
 242        None
 243    }
 244
 245    fn documentation_aside(
 246        &self,
 247        _window: &mut Window,
 248        _cx: &mut Context<Picker<Self>>,
 249    ) -> Option<DocumentationAside> {
 250        None
 251    }
 252
 253    /// Returns the index of the item whose documentation aside should be shown.
 254    /// This is used to position the aside relative to that item.
 255    /// Typically this is the hovered item, not necessarily the selected item.
 256    fn documentation_aside_index(&self) -> Option<usize> {
 257        None
 258    }
 259}
 260
 261impl<D: PickerDelegate> Focusable for Picker<D> {
 262    fn focus_handle(&self, cx: &App) -> FocusHandle {
 263        match &self.head {
 264            Head::Editor(editor) => editor.focus_handle(cx),
 265            Head::Empty(head) => head.focus_handle(cx),
 266        }
 267    }
 268}
 269
 270#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
 271enum ContainerKind {
 272    List,
 273    UniformList,
 274}
 275
 276impl<D: PickerDelegate> Picker<D> {
 277    /// A picker, which displays its matches using `gpui::uniform_list`, all matches should have the same height.
 278    /// The picker allows the user to perform search items by text.
 279    /// If `PickerDelegate::render_match` can return items with different heights, use `Picker::list`.
 280    pub fn uniform_list(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self {
 281        let head = Head::editor(
 282            delegate.placeholder_text(window, cx),
 283            Self::on_input_editor_event,
 284            window,
 285            cx,
 286        );
 287
 288        Self::new(delegate, ContainerKind::UniformList, head, window, cx)
 289    }
 290
 291    /// A picker, which displays its matches using `gpui::uniform_list`, all matches should have the same height.
 292    /// If `PickerDelegate::render_match` can return items with different heights, use `Picker::list`.
 293    pub fn nonsearchable_uniform_list(
 294        delegate: D,
 295        window: &mut Window,
 296        cx: &mut Context<Self>,
 297    ) -> Self {
 298        let head = Head::empty(Self::on_empty_head_blur, window, cx);
 299
 300        Self::new(delegate, ContainerKind::UniformList, head, window, cx)
 301    }
 302
 303    /// A picker, which displays its matches using `gpui::list`, matches can have different heights.
 304    /// The picker allows the user to perform search items by text.
 305    /// If `PickerDelegate::render_match` only returns items with the same height, use `Picker::uniform_list` as its implementation is optimized for that.
 306    pub fn nonsearchable_list(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self {
 307        let head = Head::empty(Self::on_empty_head_blur, window, cx);
 308
 309        Self::new(delegate, ContainerKind::List, head, window, cx)
 310    }
 311
 312    /// A picker, which displays its matches using `gpui::list`, matches can have different heights.
 313    /// The picker allows the user to perform search items by text.
 314    /// If `PickerDelegate::render_match` only returns items with the same height, use `Picker::uniform_list` as its implementation is optimized for that.
 315    pub fn list(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self {
 316        let head = Head::editor(
 317            delegate.placeholder_text(window, cx),
 318            Self::on_input_editor_event,
 319            window,
 320            cx,
 321        );
 322
 323        Self::new(delegate, ContainerKind::List, head, window, cx)
 324    }
 325
 326    fn new(
 327        delegate: D,
 328        container: ContainerKind,
 329        head: Head,
 330        window: &mut Window,
 331        cx: &mut Context<Self>,
 332    ) -> Self {
 333        let element_container = Self::create_element_container(container);
 334        let mut this = Self {
 335            delegate,
 336            head,
 337            element_container,
 338            pending_update_matches: None,
 339            confirm_on_update: None,
 340            width: None,
 341            widest_item: None,
 342            max_height: Some(rems(24.).into()),
 343            show_scrollbar: false,
 344            is_modal: true,
 345            picker_bounds: Rc::new(Cell::new(None)),
 346            item_bounds: Rc::new(RefCell::new(HashMap::default())),
 347        };
 348        this.update_matches("".to_string(), window, cx);
 349        // give the delegate 4ms to render the first set of suggestions.
 350        this.delegate
 351            .finalize_update_matches("".to_string(), Duration::from_millis(4), window, cx);
 352        this
 353    }
 354
 355    fn create_element_container(container: ContainerKind) -> ElementContainer {
 356        match container {
 357            ContainerKind::UniformList => {
 358                ElementContainer::UniformList(UniformListScrollHandle::new())
 359            }
 360            ContainerKind::List => {
 361                ElementContainer::List(ListState::new(0, gpui::ListAlignment::Top, px(1000.)))
 362            }
 363        }
 364    }
 365
 366    pub fn width(mut self, width: impl Into<gpui::Length>) -> Self {
 367        self.width = Some(width.into());
 368        self
 369    }
 370
 371    pub fn widest_item(mut self, ix: Option<usize>) -> Self {
 372        self.widest_item = ix;
 373        self
 374    }
 375
 376    pub fn max_height(mut self, max_height: Option<gpui::Length>) -> Self {
 377        self.max_height = max_height;
 378        self
 379    }
 380
 381    pub fn show_scrollbar(mut self, show_scrollbar: bool) -> Self {
 382        self.show_scrollbar = show_scrollbar;
 383        self
 384    }
 385
 386    pub fn modal(mut self, modal: bool) -> Self {
 387        self.is_modal = modal;
 388        self
 389    }
 390
 391    pub fn list_measure_all(mut self) -> Self {
 392        match self.element_container {
 393            ElementContainer::List(state) => {
 394                self.element_container = ElementContainer::List(state.measure_all());
 395            }
 396            _ => {}
 397        }
 398        self
 399    }
 400
 401    pub fn focus(&self, window: &mut Window, cx: &mut App) {
 402        self.focus_handle(cx).focus(window, cx);
 403    }
 404
 405    /// Handles the selecting an index, and passing the change to the delegate.
 406    /// If `fallback_direction` is set to `None`, the index will not be selected
 407    /// if the element at that index cannot be selected.
 408    /// If `fallback_direction` is set to
 409    /// `Some(..)`, the next selectable element will be selected in the
 410    /// specified direction (Down or Up), cycling through all elements until
 411    /// finding one that can be selected or returning if there are no selectable elements.
 412    /// If `scroll_to_index` is true, the new selected index will be scrolled into
 413    /// view.
 414    ///
 415    /// If some effect is bound to `selected_index_changed`, it will be executed.
 416    pub fn set_selected_index(
 417        &mut self,
 418        mut ix: usize,
 419        fallback_direction: Option<Direction>,
 420        scroll_to_index: bool,
 421        window: &mut Window,
 422        cx: &mut Context<Self>,
 423    ) {
 424        let match_count = self.delegate.match_count();
 425        if match_count == 0 {
 426            return;
 427        }
 428
 429        if let Some(bias) = fallback_direction {
 430            let mut curr_ix = ix;
 431            while !self.delegate.can_select(curr_ix, window, cx) {
 432                curr_ix = match bias {
 433                    Direction::Down => {
 434                        if curr_ix == match_count - 1 {
 435                            0
 436                        } else {
 437                            curr_ix + 1
 438                        }
 439                    }
 440                    Direction::Up => {
 441                        if curr_ix == 0 {
 442                            match_count - 1
 443                        } else {
 444                            curr_ix - 1
 445                        }
 446                    }
 447                };
 448                // There is no item that can be selected
 449                if ix == curr_ix {
 450                    return;
 451                }
 452            }
 453            ix = curr_ix;
 454        } else if !self.delegate.can_select(ix, window, cx) {
 455            return;
 456        }
 457
 458        let previous_index = self.delegate.selected_index();
 459        self.delegate.set_selected_index(ix, window, cx);
 460        let current_index = self.delegate.selected_index();
 461
 462        if previous_index != current_index {
 463            if let Some(action) = self.delegate.selected_index_changed(ix, window, cx) {
 464                action(window, cx);
 465            }
 466            if scroll_to_index {
 467                self.scroll_to_item_index(ix);
 468            }
 469        }
 470    }
 471
 472    pub fn select_next(
 473        &mut self,
 474        _: &menu::SelectNext,
 475        window: &mut Window,
 476        cx: &mut Context<Self>,
 477    ) {
 478        let query = self.query(cx);
 479        if let Some(query) = self
 480            .delegate
 481            .select_history(Direction::Down, &query, window, cx)
 482        {
 483            self.set_query(query, window, cx);
 484            return;
 485        }
 486        let count = self.delegate.match_count();
 487        if count > 0 {
 488            let index = self.delegate.selected_index();
 489            let ix = if index == count - 1 { 0 } else { index + 1 };
 490            self.set_selected_index(ix, Some(Direction::Down), true, window, cx);
 491            cx.notify();
 492        }
 493    }
 494
 495    pub fn editor_move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
 496        self.select_previous(&Default::default(), window, cx);
 497    }
 498
 499    fn select_previous(
 500        &mut self,
 501        _: &menu::SelectPrevious,
 502        window: &mut Window,
 503        cx: &mut Context<Self>,
 504    ) {
 505        let query = self.query(cx);
 506        if let Some(query) = self
 507            .delegate
 508            .select_history(Direction::Up, &query, window, cx)
 509        {
 510            self.set_query(query, window, cx);
 511            return;
 512        }
 513        let count = self.delegate.match_count();
 514        if count > 0 {
 515            let index = self.delegate.selected_index();
 516            let ix = if index == 0 { count - 1 } else { index - 1 };
 517            self.set_selected_index(ix, Some(Direction::Up), true, window, cx);
 518            cx.notify();
 519        }
 520    }
 521
 522    pub fn editor_move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
 523        self.select_next(&Default::default(), window, cx);
 524    }
 525
 526    pub fn select_first(
 527        &mut self,
 528        _: &menu::SelectFirst,
 529        window: &mut Window,
 530        cx: &mut Context<Self>,
 531    ) {
 532        let count = self.delegate.match_count();
 533        if count > 0 {
 534            self.set_selected_index(0, Some(Direction::Down), true, window, cx);
 535            cx.notify();
 536        }
 537    }
 538
 539    fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
 540        let count = self.delegate.match_count();
 541        if count > 0 {
 542            self.set_selected_index(count - 1, Some(Direction::Up), true, window, cx);
 543            cx.notify();
 544        }
 545    }
 546
 547    pub fn cycle_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 548        let count = self.delegate.match_count();
 549        let index = self.delegate.selected_index();
 550        let new_index = if index + 1 == count { 0 } else { index + 1 };
 551        self.set_selected_index(new_index, Some(Direction::Down), true, window, cx);
 552        cx.notify();
 553    }
 554
 555    pub fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
 556        if self.delegate.should_dismiss() {
 557            self.delegate.dismissed(window, cx);
 558            cx.emit(DismissEvent);
 559        }
 560    }
 561
 562    fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
 563        if self.pending_update_matches.is_some()
 564            && !self.delegate.finalize_update_matches(
 565                self.query(cx),
 566                Duration::from_millis(16),
 567                window,
 568                cx,
 569            )
 570        {
 571            self.confirm_on_update = Some(false)
 572        } else {
 573            self.pending_update_matches.take();
 574            self.do_confirm(false, window, cx);
 575        }
 576    }
 577
 578    fn secondary_confirm(
 579        &mut self,
 580        _: &menu::SecondaryConfirm,
 581        window: &mut Window,
 582        cx: &mut Context<Self>,
 583    ) {
 584        if self.pending_update_matches.is_some()
 585            && !self.delegate.finalize_update_matches(
 586                self.query(cx),
 587                Duration::from_millis(16),
 588                window,
 589                cx,
 590            )
 591        {
 592            self.confirm_on_update = Some(true)
 593        } else {
 594            self.do_confirm(true, window, cx);
 595        }
 596    }
 597
 598    fn confirm_input(&mut self, input: &ConfirmInput, window: &mut Window, cx: &mut Context<Self>) {
 599        self.delegate.confirm_input(input.secondary, window, cx);
 600    }
 601
 602    fn confirm_completion(
 603        &mut self,
 604        _: &ConfirmCompletion,
 605        window: &mut Window,
 606        cx: &mut Context<Self>,
 607    ) {
 608        if let Some(new_query) = self.delegate.confirm_completion(self.query(cx), window, cx) {
 609            self.set_query(new_query, window, cx);
 610        } else {
 611            cx.propagate()
 612        }
 613    }
 614
 615    fn handle_click(
 616        &mut self,
 617        ix: usize,
 618        secondary: bool,
 619        window: &mut Window,
 620        cx: &mut Context<Self>,
 621    ) {
 622        cx.stop_propagation();
 623        window.prevent_default();
 624        self.set_selected_index(ix, None, false, window, cx);
 625        self.do_confirm(secondary, window, cx)
 626    }
 627
 628    fn do_confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Self>) {
 629        if let Some(update_query) = self.delegate.confirm_update_query(window, cx) {
 630            self.set_query(update_query, window, cx);
 631            self.set_selected_index(0, Some(Direction::Down), false, window, cx);
 632        } else {
 633            self.delegate.confirm(secondary, window, cx)
 634        }
 635    }
 636
 637    fn on_input_editor_event(
 638        &mut self,
 639        _: &Entity<Editor>,
 640        event: &editor::EditorEvent,
 641        window: &mut Window,
 642        cx: &mut Context<Self>,
 643    ) {
 644        let Head::Editor(editor) = &self.head else {
 645            panic!("unexpected call");
 646        };
 647        match event {
 648            editor::EditorEvent::BufferEdited => {
 649                let query = editor.read(cx).text(cx);
 650                self.update_matches(query, window, cx);
 651            }
 652            editor::EditorEvent::Blurred => {
 653                if self.is_modal && window.is_window_active() {
 654                    self.cancel(&menu::Cancel, window, cx);
 655                }
 656            }
 657            _ => {}
 658        }
 659    }
 660
 661    fn on_empty_head_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 662        let Head::Empty(_) = &self.head else {
 663            panic!("unexpected call");
 664        };
 665        if window.is_window_active() {
 666            self.cancel(&menu::Cancel, window, cx);
 667        }
 668    }
 669
 670    pub fn refresh_placeholder(&mut self, window: &mut Window, cx: &mut App) {
 671        match &self.head {
 672            Head::Editor(editor) => {
 673                let placeholder = self.delegate.placeholder_text(window, cx);
 674                editor.update(cx, |editor, cx| {
 675                    editor.set_placeholder_text(placeholder.as_ref(), window, cx);
 676                    cx.notify();
 677                });
 678            }
 679            Head::Empty(_) => {}
 680        }
 681    }
 682
 683    pub fn refresh(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 684        let query = self.query(cx);
 685        self.update_matches(query, window, cx);
 686    }
 687
 688    pub fn update_matches(&mut self, query: String, window: &mut Window, cx: &mut Context<Self>) {
 689        let delegate_pending_update_matches = self.delegate.update_matches(query, window, cx);
 690
 691        self.matches_updated(window, cx);
 692        // This struct ensures that we can synchronously drop the task returned by the
 693        // delegate's `update_matches` method and the task that the picker is spawning.
 694        // If we simply capture the delegate's task into the picker's task, when the picker's
 695        // task gets synchronously dropped, the delegate's task would keep running until
 696        // the picker's task has a chance of being scheduled, because dropping a task happens
 697        // asynchronously.
 698        self.pending_update_matches = Some(PendingUpdateMatches {
 699            delegate_update_matches: Some(delegate_pending_update_matches),
 700            _task: cx.spawn_in(window, async move |this, cx| {
 701                let delegate_pending_update_matches = this.update(cx, |this, _| {
 702                    this.pending_update_matches
 703                        .as_mut()
 704                        .unwrap()
 705                        .delegate_update_matches
 706                        .take()
 707                        .unwrap()
 708                })?;
 709                delegate_pending_update_matches.await;
 710                this.update_in(cx, |this, window, cx| {
 711                    this.matches_updated(window, cx);
 712                })
 713            }),
 714        });
 715    }
 716
 717    fn matches_updated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 718        if let ElementContainer::List(state) = &mut self.element_container {
 719            state.reset(self.delegate.match_count());
 720        }
 721
 722        let index = self.delegate.selected_index();
 723        self.scroll_to_item_index(index);
 724        self.pending_update_matches = None;
 725        if let Some(secondary) = self.confirm_on_update.take() {
 726            self.do_confirm(secondary, window, cx);
 727        }
 728        cx.notify();
 729    }
 730
 731    pub fn query(&self, cx: &App) -> String {
 732        match &self.head {
 733            Head::Editor(editor) => editor.read(cx).text(cx),
 734            Head::Empty(_) => "".to_string(),
 735        }
 736    }
 737
 738    pub fn set_query(&self, query: impl Into<Arc<str>>, window: &mut Window, cx: &mut App) {
 739        if let Head::Editor(editor) = &self.head {
 740            editor.update(cx, |editor, cx| {
 741                editor.set_text(query, window, cx);
 742                let editor_offset = editor.buffer().read(cx).len(cx);
 743                editor.change_selections(
 744                    SelectionEffects::scroll(Autoscroll::Next),
 745                    window,
 746                    cx,
 747                    |s| s.select_ranges(Some(editor_offset..editor_offset)),
 748                );
 749            });
 750        }
 751    }
 752
 753    fn scroll_to_item_index(&mut self, ix: usize) {
 754        match &mut self.element_container {
 755            ElementContainer::List(state) => state.scroll_to_reveal_item(ix),
 756            ElementContainer::UniformList(scroll_handle) => {
 757                scroll_handle.scroll_to_item(ix, ScrollStrategy::Nearest)
 758            }
 759        }
 760    }
 761
 762    fn render_element(
 763        &self,
 764        window: &mut Window,
 765        cx: &mut Context<Self>,
 766        ix: usize,
 767    ) -> impl IntoElement + use<D> {
 768        let item_bounds = self.item_bounds.clone();
 769
 770        div()
 771            .id(("item", ix))
 772            .cursor_pointer()
 773            .child(
 774                canvas(
 775                    move |bounds, _window, _cx| {
 776                        item_bounds.borrow_mut().insert(ix, bounds);
 777                    },
 778                    |_bounds, _state, _window, _cx| {},
 779                )
 780                .size_full()
 781                .absolute()
 782                .top_0()
 783                .left_0(),
 784            )
 785            .on_click(cx.listener(move |this, event: &ClickEvent, window, cx| {
 786                this.handle_click(ix, event.modifiers().secondary(), window, cx)
 787            }))
 788            // As of this writing, GPUI intercepts `ctrl-[mouse-event]`s on macOS
 789            // and produces right mouse button events. This matches platforms norms
 790            // but means that UIs which depend on holding ctrl down (such as the tab
 791            // switcher) can't be clicked on. Hence, this handler.
 792            .on_mouse_up(
 793                MouseButton::Right,
 794                cx.listener(move |this, event: &MouseUpEvent, window, cx| {
 795                    // We specifically want to use the platform key here, as
 796                    // ctrl will already be held down for the tab switcher.
 797                    this.handle_click(ix, event.modifiers.platform, window, cx)
 798                }),
 799            )
 800            .children(self.delegate.render_match(
 801                ix,
 802                ix == self.delegate.selected_index(),
 803                window,
 804                cx,
 805            ))
 806            .when(
 807                self.delegate.separators_after_indices().contains(&ix),
 808                |picker| {
 809                    picker
 810                        .border_color(cx.theme().colors().border_variant)
 811                        .border_b_1()
 812                        .py(px(-1.0))
 813                },
 814            )
 815    }
 816
 817    fn render_element_container(&self, cx: &mut Context<Self>) -> impl IntoElement {
 818        let sizing_behavior = if self.max_height.is_some() {
 819            ListSizingBehavior::Infer
 820        } else {
 821            ListSizingBehavior::Auto
 822        };
 823
 824        match &self.element_container {
 825            ElementContainer::UniformList(scroll_handle) => uniform_list(
 826                "candidates",
 827                self.delegate.match_count(),
 828                cx.processor(move |picker, visible_range: Range<usize>, window, cx| {
 829                    visible_range
 830                        .map(|ix| picker.render_element(window, cx, ix))
 831                        .collect()
 832                }),
 833            )
 834            .with_sizing_behavior(sizing_behavior)
 835            .when_some(self.widest_item, |el, widest_item| {
 836                el.with_width_from_item(Some(widest_item))
 837            })
 838            .flex_grow()
 839            .py_1()
 840            .track_scroll(&scroll_handle)
 841            .into_any_element(),
 842            ElementContainer::List(state) => list(
 843                state.clone(),
 844                cx.processor(|this, ix, window, cx| {
 845                    this.render_element(window, cx, ix).into_any_element()
 846                }),
 847            )
 848            .with_sizing_behavior(sizing_behavior)
 849            .flex_grow()
 850            .py_2()
 851            .into_any_element(),
 852        }
 853    }
 854
 855    #[cfg(any(test, feature = "test-support"))]
 856    pub fn logical_scroll_top_index(&self) -> usize {
 857        match &self.element_container {
 858            ElementContainer::List(state) => state.logical_scroll_top().item_ix,
 859            ElementContainer::UniformList(scroll_handle) => {
 860                scroll_handle.logical_scroll_top_index()
 861            }
 862        }
 863    }
 864}
 865
 866impl<D: PickerDelegate> EventEmitter<DismissEvent> for Picker<D> {}
 867impl<D: PickerDelegate> ModalView for Picker<D> {}
 868
 869impl<D: PickerDelegate> Render for Picker<D> {
 870    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 871        let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
 872        let window_size = window.viewport_size();
 873        let rem_size = window.rem_size();
 874        let is_wide_window = window_size.width / rem_size > rems_from_px(800.).0;
 875
 876        let aside = self.delegate.documentation_aside(window, cx);
 877
 878        let editor_position = self.delegate.editor_position();
 879        let picker_bounds = self.picker_bounds.clone();
 880        let menu = v_flex()
 881            .key_context("Picker")
 882            .size_full()
 883            .when_some(self.width, |el, width| el.w(width))
 884            .overflow_hidden()
 885            .child(
 886                canvas(
 887                    move |bounds, _window, _cx| {
 888                        picker_bounds.set(Some(bounds));
 889                    },
 890                    |_bounds, _state, _window, _cx| {},
 891                )
 892                .size_full()
 893                .absolute()
 894                .top_0()
 895                .left_0(),
 896            )
 897            // This is a bit of a hack to remove the modal styling when we're rendering the `Picker`
 898            // as a part of a modal rather than the entire modal.
 899            //
 900            // We should revisit how the `Picker` is styled to make it more composable.
 901            .when(self.is_modal, |this| this.elevation_3(cx))
 902            .on_action(cx.listener(Self::select_next))
 903            .on_action(cx.listener(Self::select_previous))
 904            .on_action(cx.listener(Self::editor_move_down))
 905            .on_action(cx.listener(Self::editor_move_up))
 906            .on_action(cx.listener(Self::select_first))
 907            .on_action(cx.listener(Self::select_last))
 908            .on_action(cx.listener(Self::cancel))
 909            .on_action(cx.listener(Self::confirm))
 910            .on_action(cx.listener(Self::secondary_confirm))
 911            .on_action(cx.listener(Self::confirm_completion))
 912            .on_action(cx.listener(Self::confirm_input))
 913            .children(match &self.head {
 914                Head::Editor(editor) => {
 915                    if editor_position == PickerEditorPosition::Start {
 916                        Some(self.delegate.render_editor(&editor.clone(), window, cx))
 917                    } else {
 918                        None
 919                    }
 920                }
 921                Head::Empty(empty_head) => Some(div().child(empty_head.clone())),
 922            })
 923            .when(self.delegate.match_count() > 0, |el| {
 924                el.child(
 925                    v_flex()
 926                        .id("element-container")
 927                        .relative()
 928                        .flex_grow()
 929                        .when_some(self.max_height, |div, max_h| div.max_h(max_h))
 930                        .overflow_hidden()
 931                        .children(self.delegate.render_header(window, cx))
 932                        .child(self.render_element_container(cx))
 933                        .when(self.show_scrollbar, |this| {
 934                            let base_scrollbar_config =
 935                                Scrollbars::new(ScrollAxes::Vertical).width_sm();
 936
 937                            this.map(|this| match &self.element_container {
 938                                ElementContainer::List(state) => this.custom_scrollbars(
 939                                    base_scrollbar_config.tracked_scroll_handle(state),
 940                                    window,
 941                                    cx,
 942                                ),
 943                                ElementContainer::UniformList(state) => this.custom_scrollbars(
 944                                    base_scrollbar_config.tracked_scroll_handle(state),
 945                                    window,
 946                                    cx,
 947                                ),
 948                            })
 949                        }),
 950                )
 951            })
 952            .when(self.delegate.match_count() == 0, |el| {
 953                el.when_some(self.delegate.no_matches_text(window, cx), |el, text| {
 954                    el.child(
 955                        v_flex().flex_grow().py_2().child(
 956                            ListItem::new("empty_state")
 957                                .inset(true)
 958                                .spacing(ListItemSpacing::Sparse)
 959                                .disabled(true)
 960                                .child(Label::new(text).color(Color::Muted)),
 961                        ),
 962                    )
 963                })
 964            })
 965            .children(self.delegate.render_footer(window, cx))
 966            .children(match &self.head {
 967                Head::Editor(editor) => {
 968                    if editor_position == PickerEditorPosition::End {
 969                        Some(self.delegate.render_editor(&editor.clone(), window, cx))
 970                    } else {
 971                        None
 972                    }
 973                }
 974                Head::Empty(empty_head) => Some(div().child(empty_head.clone())),
 975            });
 976
 977        let Some(aside) = aside else {
 978            return menu;
 979        };
 980
 981        let render_aside = |aside: DocumentationAside, cx: &mut Context<Self>| {
 982            WithRemSize::new(ui_font_size)
 983                .occlude()
 984                .elevation_2(cx)
 985                .w_full()
 986                .p_2()
 987                .overflow_hidden()
 988                .when(is_wide_window, |this| this.max_w_96())
 989                .when(!is_wide_window, |this| this.max_w_48())
 990                .child((aside.render)(cx))
 991        };
 992
 993        if is_wide_window {
 994            let aside_index = self.delegate.documentation_aside_index();
 995            let picker_bounds = self.picker_bounds.get();
 996            let item_bounds =
 997                aside_index.and_then(|ix| self.item_bounds.borrow().get(&ix).copied());
 998
 999            let item_position = match (picker_bounds, item_bounds) {
1000                (Some(picker_bounds), Some(item_bounds)) => {
1001                    let relative_top = item_bounds.origin.y - picker_bounds.origin.y;
1002                    let height = item_bounds.size.height;
1003                    Some((relative_top, height))
1004                }
1005                _ => None,
1006            };
1007
1008            div()
1009                .relative()
1010                .child(menu)
1011                // Only render the aside once we have bounds to avoid flicker
1012                .when_some(item_position, |this, (top, height)| {
1013                    this.child(
1014                        h_flex()
1015                            .absolute()
1016                            .when(aside.side == DocumentationSide::Left, |el| {
1017                                el.right_full().mr_1()
1018                            })
1019                            .when(aside.side == DocumentationSide::Right, |el| {
1020                                el.left_full().ml_1()
1021                            })
1022                            .top(top)
1023                            .h(height)
1024                            .child(render_aside(aside, cx)),
1025                    )
1026                })
1027        } else {
1028            v_flex()
1029                .w_full()
1030                .gap_1()
1031                .justify_end()
1032                .child(render_aside(aside, cx))
1033                .child(menu)
1034        }
1035    }
1036}