mod head;pub mod highlighted_match_with_paths;pub mod popover_menu; use anyhow::Result; use gpui::{ Action, AnyElement, App, Bounds, ClickEvent, Context, DismissEvent, EventEmitter, FocusHandle, Focusable, Length, ListSizingBehavior, ListState, MouseButton, MouseUpEvent, Pixels, Render, ScrollStrategy, Task, UniformListScrollHandle, Window, actions, canvas, div, list, prelude::*, uniform_list,};use head::Head;use schemars::JsonSchema;use serde::Deserialize;use std::{ cell::Cell, cell::RefCell, collections::HashMap, ops::Range, rc::Rc, sync::Arc, time::Duration,};use theme::ThemeSettings;use ui::{ Color, Divider, DocumentationAside, DocumentationSide, Label, ListItem, ListItemSpacing, ScrollAxes, Scrollbars, WithScrollbar, prelude::*, utils::WithRemSize, v_flex,};use ui_input::{ErasedEditor, ErasedEditorEvent};use workspace::{ModalView, item::Settings};use zed_actions::editor::{MoveDown, MoveUp}; enum ElementContainer { List(ListState), UniformList(UniformListScrollHandle),} pub enum Direction { Up, Down,} actions!( picker, [ /// Confirms the selected completion in the picker. ConfirmCompletion ]); /// ConfirmInput is an alternative editor action which - instead of selecting active picker entry - treats pickers editor input literally,/// performing some kind of action on it.#[derive(Clone, PartialEq, Deserialize, JsonSchema, Default, Action)]#[action(namespace = picker)]#[serde(deny_unknown_fields)]pub struct ConfirmInput { pub secondary: bool,} struct PendingUpdateMatches { delegate_update_matches: Option<Task<()>>, _task: Task<Result<()>>,} pub struct Picker<D: PickerDelegate> { pub delegate: D, element_container: ElementContainer, head: Head, pending_update_matches: Option<PendingUpdateMatches>, confirm_on_update: Option<bool>, width: Option<Length>, widest_item: Option<usize>, max_height: Option<Length>, /// An external control to display a scrollbar in the `Picker`. show_scrollbar: bool, /// Whether the `Picker` is rendered as a self-contained modal. /// /// Set this to `false` when rendering the `Picker` as part of a larger modal. is_modal: bool, /// Bounds tracking for the picker container (for aside positioning) picker_bounds: Rc<Cell<Option<Bounds<Pixels>>>>, /// Bounds tracking for items (for aside positioning) - maps item index to bounds item_bounds: Rc<RefCell<HashMap<usize, Bounds<Pixels>>>>,} #[derive(Debug, Default, Clone, Copy, PartialEq)]pub enum PickerEditorPosition { #[default] /// Render the editor at the start of the picker. Usually the top Start, /// Render the editor at the end of the picker. Usually the bottom End,} pub trait PickerDelegate: Sized + 'static { type ListItem: IntoElement; fn match_count(&self) -> usize; fn selected_index(&self) -> usize; fn separators_after_indices(&self) -> Vec<usize> { Vec::new() } fn set_selected_index( &mut self, ix: usize, window: &mut Window, cx: &mut Context<Picker<Self>>, ); /// Called before the picker handles `SelectPrevious` or `SelectNext`. Return `Some(query)` to /// set a new query and prevent the default selection behavior. fn select_history( &mut self, _direction: Direction, _query: &str, _window: &mut Window, _cx: &mut App, ) -> Option<String> { None } fn can_select( &mut self, _ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> bool { true } // Allows binding some optional effect to when the selection changes. fn selected_index_changed( &self, _ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> Option<Box<dyn Fn(&mut Window, &mut App) + 'static>> { None } fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str>; fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> { Some("No matches".into()) } fn update_matches( &mut self, query: String, window: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Task<()>; // Delegates that support this method (e.g. the CommandPalette) can chose to block on any background // work for up to `duration` to try and get a result synchronously. // This avoids a flash of an empty command-palette on cmd-shift-p, and lets workspace::SendKeystrokes // mostly work when dismissing a palette. fn finalize_update_matches( &mut self, _query: String, _duration: Duration, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> bool { false } /// Override if you want to have <enter> update the query instead of confirming. fn confirm_update_query( &mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> Option<String> { None } fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>); /// Instead of interacting with currently selected entry, treats editor input literally, /// performing some kind of action on it. fn confirm_input( &mut self, _secondary: bool, _window: &mut Window, _: &mut Context<Picker<Self>>, ) { } fn dismissed(&mut self, window: &mut Window, cx: &mut Context<Picker<Self>>); fn should_dismiss(&self) -> bool { true } fn confirm_completion( &mut self, _query: String, _window: &mut Window, _: &mut Context<Picker<Self>>, ) -> Option<String> { None } fn editor_position(&self) -> PickerEditorPosition { PickerEditorPosition::default() } fn render_editor( &self, editor: &Arc<dyn ErasedEditor>, window: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Div { v_flex() .when( self.editor_position() == PickerEditorPosition::End, |this| this.child(Divider::horizontal()), ) .child( h_flex() .overflow_hidden() .flex_none() .h_9() .px_2p5() .child(editor.render(window, cx)), ) .when( self.editor_position() == PickerEditorPosition::Start, |this| this.child(Divider::horizontal()), ) } fn render_match( &self, ix: usize, selected: bool, window: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Option<Self::ListItem>; fn render_header( &self, _window: &mut Window, _: &mut Context<Picker<Self>>, ) -> Option<AnyElement> { None } fn render_footer( &self, _window: &mut Window, _: &mut Context<Picker<Self>>, ) -> Option<AnyElement> { None } fn documentation_aside( &self, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> Option<DocumentationAside> { None } /// Returns the index of the item whose documentation aside should be shown. /// This is used to position the aside relative to that item. /// Typically this is the hovered item, not necessarily the selected item. fn documentation_aside_index(&self) -> Option<usize> { None }} impl<D: PickerDelegate> Focusable for Picker<D> { fn focus_handle(&self, cx: &App) -> FocusHandle { match &self.head { Head::Editor(editor) => editor.focus_handle(cx), Head::Empty(head) => head.focus_handle(cx), } }} #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]enum ContainerKind { List, UniformList,} impl<D: PickerDelegate> Picker<D> { /// A picker, which displays its matches using `gpui::uniform_list`, all matches should have the same height. /// The picker allows the user to perform search items by text. /// If `PickerDelegate::render_match` can return items with different heights, use `Picker::list`. pub fn uniform_list(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self { let head = Head::editor( delegate.placeholder_text(window, cx), Self::on_input_editor_event, window, cx, ); Self::new(delegate, ContainerKind::UniformList, head, window, cx) } /// A picker, which displays its matches using `gpui::uniform_list`, all matches should have the same height. /// If `PickerDelegate::render_match` can return items with different heights, use `Picker::list`. pub fn nonsearchable_uniform_list( delegate: D, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let head = Head::empty(Self::on_empty_head_blur, window, cx); Self::new(delegate, ContainerKind::UniformList, head, window, cx) } /// A picker, which displays its matches using `gpui::list`, matches can have different heights. /// The picker allows the user to perform search items by text. /// If `PickerDelegate::render_match` only returns items with the same height, use `Picker::uniform_list` as its implementation is optimized for that. pub fn nonsearchable_list(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self { let head = Head::empty(Self::on_empty_head_blur, window, cx); Self::new(delegate, ContainerKind::List, head, window, cx) } /// A picker, which displays its matches using `gpui::list`, matches can have different heights. /// The picker allows the user to perform search items by text. /// If `PickerDelegate::render_match` only returns items with the same height, use `Picker::uniform_list` as its implementation is optimized for that. pub fn list(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self { let head = Head::editor( delegate.placeholder_text(window, cx), Self::on_input_editor_event, window, cx, ); Self::new(delegate, ContainerKind::List, head, window, cx) } fn new( delegate: D, container: ContainerKind, head: Head, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let element_container = Self::create_element_container(container); let mut this = Self { delegate, head, element_container, pending_update_matches: None, confirm_on_update: None, width: None, widest_item: None, max_height: Some(rems(24.).into()), show_scrollbar: false, is_modal: true, picker_bounds: Rc::new(Cell::new(None)), item_bounds: Rc::new(RefCell::new(HashMap::default())), }; this.update_matches("".to_string(), window, cx); // give the delegate 4ms to render the first set of suggestions. this.delegate .finalize_update_matches("".to_string(), Duration::from_millis(4), window, cx); this } fn create_element_container(container: ContainerKind) -> ElementContainer { match container { ContainerKind::UniformList => { ElementContainer::UniformList(UniformListScrollHandle::new()) } ContainerKind::List => { ElementContainer::List(ListState::new(0, gpui::ListAlignment::Top, px(1000.))) } } } pub fn width(mut self, width: impl Into<gpui::Length>) -> Self { self.width = Some(width.into()); self } pub fn widest_item(mut self, ix: Option<usize>) -> Self { self.widest_item = ix; self } pub fn max_height(mut self, max_height: Option<gpui::Length>) -> Self { self.max_height = max_height; self } pub fn show_scrollbar(mut self, show_scrollbar: bool) -> Self { self.show_scrollbar = show_scrollbar; self } pub fn modal(mut self, modal: bool) -> Self { self.is_modal = modal; self } pub fn list_measure_all(mut self) -> Self { match self.element_container { ElementContainer::List(state) => { self.element_container = ElementContainer::List(state.measure_all()); } _ => {} } self } pub fn focus(&self, window: &mut Window, cx: &mut App) { self.focus_handle(cx).focus(window, cx); } /// Handles the selecting an index, and passing the change to the delegate. /// If `fallback_direction` is set to `None`, the index will not be selected /// if the element at that index cannot be selected. /// If `fallback_direction` is set to /// `Some(..)`, the next selectable element will be selected in the /// specified direction (Down or Up), cycling through all elements until /// finding one that can be selected or returning if there are no selectable elements. /// If `scroll_to_index` is true, the new selected index will be scrolled into /// view. /// /// If some effect is bound to `selected_index_changed`, it will be executed. pub fn set_selected_index( &mut self, mut ix: usize, fallback_direction: Option<Direction>, scroll_to_index: bool, window: &mut Window, cx: &mut Context<Self>, ) { let match_count = self.delegate.match_count(); if match_count == 0 { return; } if let Some(bias) = fallback_direction { let mut curr_ix = ix; while !self.delegate.can_select(curr_ix, window, cx) { curr_ix = match bias { Direction::Down => { if curr_ix == match_count - 1 { 0 } else { curr_ix + 1 } } Direction::Up => { if curr_ix == 0 { match_count - 1 } else { curr_ix - 1 } } }; // There is no item that can be selected if ix == curr_ix { return; } } ix = curr_ix; } else if !self.delegate.can_select(ix, window, cx) { return; } let previous_index = self.delegate.selected_index(); self.delegate.set_selected_index(ix, window, cx); let current_index = self.delegate.selected_index(); if previous_index != current_index { if let Some(action) = self.delegate.selected_index_changed(ix, window, cx) { action(window, cx); } if scroll_to_index { self.scroll_to_item_index(ix); } } } pub fn select_next( &mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>, ) { let query = self.query(cx); if let Some(query) = self .delegate .select_history(Direction::Down, &query, window, cx) { self.set_query(&query, window, cx); return; } let count = self.delegate.match_count(); if count > 0 { let index = self.delegate.selected_index(); let ix = if index == count - 1 { 0 } else { index + 1 }; self.set_selected_index(ix, Some(Direction::Down), true, window, cx); cx.notify(); } } pub fn editor_move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) { self.select_previous(&Default::default(), window, cx); } fn select_previous( &mut self, _: &menu::SelectPrevious, window: &mut Window,Lines 1–500 of 1023. Load more
mod head; pub mod highlighted_match_with_paths; pub mod popover_menu; use anyhow::Result; use gpui::{ Action, AnyElement, App, Bounds, ClickEvent, Context, DismissEvent, EventEmitter, FocusHandle, Focusable, Length, ListSizingBehavior, ListState, MouseButton, MouseUpEvent, Pixels, Render, ScrollStrategy, Task, UniformListScrollHandle, Window, actions, canvas, div, list, prelude::*, uniform_list, }; use head::Head; use schemars::JsonSchema; use serde::Deserialize; use std::{ cell::Cell, cell::RefCell, collections::HashMap, ops::Range, rc::Rc, sync::Arc, time::Duration, }; use theme::ThemeSettings; use ui::{ Color, Divider, DocumentationAside, DocumentationSide, Label, ListItem, ListItemSpacing, ScrollAxes, Scrollbars, WithScrollbar, prelude::*, utils::WithRemSize, v_flex, }; use ui_input::{ErasedEditor, ErasedEditorEvent}; use workspace::{ModalView, item::Settings}; use zed_actions::editor::{MoveDown, MoveUp}; enum ElementContainer { List(ListState), UniformList(UniformListScrollHandle), } pub enum Direction { Up, Down, } actions!( picker, [ /// Confirms the selected completion in the picker. ConfirmCompletion ] ); /// ConfirmInput is an alternative editor action which - instead of selecting active picker entry - treats pickers editor input literally, /// performing some kind of action on it. #[derive(Clone, PartialEq, Deserialize, JsonSchema, Default, Action)] #[action(namespace = picker)] #[serde(deny_unknown_fields)] pub struct ConfirmInput { pub secondary: bool, } struct PendingUpdateMatches { delegate_update_matches: Option<Task<()>>, _task: Task<Result<()>>, } pub struct Picker<D: PickerDelegate> { pub delegate: D, element_container: ElementContainer, head: Head, pending_update_matches: Option<PendingUpdateMatches>, confirm_on_update: Option<bool>, width: Option<Length>, widest_item: Option<usize>, max_height: Option<Length>, /// An external control to display a scrollbar in the `Picker`. show_scrollbar: bool, /// Whether the `Picker` is rendered as a self-contained modal. /// /// Set this to `false` when rendering the `Picker` as part of a larger modal. is_modal: bool, /// Bounds tracking for the picker container (for aside positioning) picker_bounds: Rc<Cell<Option<Bounds<Pixels>>>>, /// Bounds tracking for items (for aside positioning) - maps item index to bounds item_bounds: Rc<RefCell<HashMap<usize, Bounds<Pixels>>>>, } #[derive(Debug, Default, Clone, Copy, PartialEq)] pub enum PickerEditorPosition { #[default] /// Render the editor at the start of the picker. Usually the top Start, /// Render the editor at the end of the picker. Usually the bottom End, } pub trait PickerDelegate: Sized + 'static { type ListItem: IntoElement; fn match_count(&self) -> usize; fn selected_index(&self) -> usize; fn separators_after_indices(&self) -> Vec<usize> { Vec::new() } fn set_selected_index( &mut self, ix: usize, window: &mut Window, cx: &mut Context<Picker<Self>>, ); /// Called before the picker handles `SelectPrevious` or `SelectNext`. Return `Some(query)` to /// set a new query and prevent the default selection behavior. fn select_history( &mut self, _direction: Direction, _query: &str, _window: &mut Window, _cx: &mut App, ) -> Option<String> { None } fn can_select( &mut self, _ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> bool { true } // Allows binding some optional effect to when the selection changes. fn selected_index_changed( &self, _ix: usize, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> Option<Box<dyn Fn(&mut Window, &mut App) + 'static>> { None } fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str>; fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> { Some("No matches".into()) } fn update_matches( &mut self, query: String, window: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Task<()>; // Delegates that support this method (e.g. the CommandPalette) can chose to block on any background // work for up to `duration` to try and get a result synchronously. // This avoids a flash of an empty command-palette on cmd-shift-p, and lets workspace::SendKeystrokes // mostly work when dismissing a palette. fn finalize_update_matches( &mut self, _query: String, _duration: Duration, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> bool { false } /// Override if you want to have <enter> update the query instead of confirming. fn confirm_update_query( &mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> Option<String> { None } fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>); /// Instead of interacting with currently selected entry, treats editor input literally, /// performing some kind of action on it. fn confirm_input( &mut self, _secondary: bool, _window: &mut Window, _: &mut Context<Picker<Self>>, ) { } fn dismissed(&mut self, window: &mut Window, cx: &mut Context<Picker<Self>>); fn should_dismiss(&self) -> bool { true } fn confirm_completion( &mut self, _query: String, _window: &mut Window, _: &mut Context<Picker<Self>>, ) -> Option<String> { None } fn editor_position(&self) -> PickerEditorPosition { PickerEditorPosition::default() } fn render_editor( &self, editor: &Arc<dyn ErasedEditor>, window: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Div { v_flex() .when( self.editor_position() == PickerEditorPosition::End, |this| this.child(Divider::horizontal()), ) .child( h_flex() .overflow_hidden() .flex_none() .h_9() .px_2p5() .child(editor.render(window, cx)), ) .when( self.editor_position() == PickerEditorPosition::Start, |this| this.child(Divider::horizontal()), ) } fn render_match( &self, ix: usize, selected: bool, window: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Option<Self::ListItem>; fn render_header( &self, _window: &mut Window, _: &mut Context<Picker<Self>>, ) -> Option<AnyElement> { None } fn render_footer( &self, _window: &mut Window, _: &mut Context<Picker<Self>>, ) -> Option<AnyElement> { None } fn documentation_aside( &self, _window: &mut Window, _cx: &mut Context<Picker<Self>>, ) -> Option<DocumentationAside> { None } /// Returns the index of the item whose documentation aside should be shown. /// This is used to position the aside relative to that item. /// Typically this is the hovered item, not necessarily the selected item. fn documentation_aside_index(&self) -> Option<usize> { None } } impl<D: PickerDelegate> Focusable for Picker<D> { fn focus_handle(&self, cx: &App) -> FocusHandle { match &self.head { Head::Editor(editor) => editor.focus_handle(cx), Head::Empty(head) => head.focus_handle(cx), } } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] enum ContainerKind { List, UniformList, } impl<D: PickerDelegate> Picker<D> { /// A picker, which displays its matches using `gpui::uniform_list`, all matches should have the same height. /// The picker allows the user to perform search items by text. /// If `PickerDelegate::render_match` can return items with different heights, use `Picker::list`. pub fn uniform_list(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self { let head = Head::editor( delegate.placeholder_text(window, cx), Self::on_input_editor_event, window, cx, ); Self::new(delegate, ContainerKind::UniformList, head, window, cx) } /// A picker, which displays its matches using `gpui::uniform_list`, all matches should have the same height. /// If `PickerDelegate::render_match` can return items with different heights, use `Picker::list`. pub fn nonsearchable_uniform_list( delegate: D, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let head = Head::empty(Self::on_empty_head_blur, window, cx); Self::new(delegate, ContainerKind::UniformList, head, window, cx) } /// A picker, which displays its matches using `gpui::list`, matches can have different heights. /// The picker allows the user to perform search items by text. /// If `PickerDelegate::render_match` only returns items with the same height, use `Picker::uniform_list` as its implementation is optimized for that. pub fn nonsearchable_list(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self { let head = Head::empty(Self::on_empty_head_blur, window, cx); Self::new(delegate, ContainerKind::List, head, window, cx) } /// A picker, which displays its matches using `gpui::list`, matches can have different heights. /// The picker allows the user to perform search items by text. /// If `PickerDelegate::render_match` only returns items with the same height, use `Picker::uniform_list` as its implementation is optimized for that. pub fn list(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self { let head = Head::editor( delegate.placeholder_text(window, cx), Self::on_input_editor_event, window, cx, ); Self::new(delegate, ContainerKind::List, head, window, cx) } fn new( delegate: D, container: ContainerKind, head: Head, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let element_container = Self::create_element_container(container); let mut this = Self { delegate, head, element_container, pending_update_matches: None, confirm_on_update: None, width: None, widest_item: None, max_height: Some(rems(24.).into()), show_scrollbar: false, is_modal: true, picker_bounds: Rc::new(Cell::new(None)), item_bounds: Rc::new(RefCell::new(HashMap::default())), }; this.update_matches("".to_string(), window, cx); // give the delegate 4ms to render the first set of suggestions. this.delegate .finalize_update_matches("".to_string(), Duration::from_millis(4), window, cx); this } fn create_element_container(container: ContainerKind) -> ElementContainer { match container { ContainerKind::UniformList => { ElementContainer::UniformList(UniformListScrollHandle::new()) } ContainerKind::List => { ElementContainer::List(ListState::new(0, gpui::ListAlignment::Top, px(1000.))) } } } pub fn width(mut self, width: impl Into<gpui::Length>) -> Self { self.width = Some(width.into()); self } pub fn widest_item(mut self, ix: Option<usize>) -> Self { self.widest_item = ix; self } pub fn max_height(mut self, max_height: Option<gpui::Length>) -> Self { self.max_height = max_height; self } pub fn show_scrollbar(mut self, show_scrollbar: bool) -> Self { self.show_scrollbar = show_scrollbar; self } pub fn modal(mut self, modal: bool) -> Self { self.is_modal = modal; self } pub fn list_measure_all(mut self) -> Self { match self.element_container { ElementContainer::List(state) => { self.element_container = ElementContainer::List(state.measure_all()); } _ => {} } self } pub fn focus(&self, window: &mut Window, cx: &mut App) { self.focus_handle(cx).focus(window, cx); } /// Handles the selecting an index, and passing the change to the delegate. /// If `fallback_direction` is set to `None`, the index will not be selected /// if the element at that index cannot be selected. /// If `fallback_direction` is set to /// `Some(..)`, the next selectable element will be selected in the /// specified direction (Down or Up), cycling through all elements until /// finding one that can be selected or returning if there are no selectable elements. /// If `scroll_to_index` is true, the new selected index will be scrolled into /// view. /// /// If some effect is bound to `selected_index_changed`, it will be executed. pub fn set_selected_index( &mut self, mut ix: usize, fallback_direction: Option<Direction>, scroll_to_index: bool, window: &mut Window, cx: &mut Context<Self>, ) { let match_count = self.delegate.match_count(); if match_count == 0 { return; } if let Some(bias) = fallback_direction { let mut curr_ix = ix; while !self.delegate.can_select(curr_ix, window, cx) { curr_ix = match bias { Direction::Down => { if curr_ix == match_count - 1 { 0 } else { curr_ix + 1 } } Direction::Up => { if curr_ix == 0 { match_count - 1 } else { curr_ix - 1 } } }; // There is no item that can be selected if ix == curr_ix { return; } } ix = curr_ix; } else if !self.delegate.can_select(ix, window, cx) { return; } let previous_index = self.delegate.selected_index(); self.delegate.set_selected_index(ix, window, cx); let current_index = self.delegate.selected_index(); if previous_index != current_index { if let Some(action) = self.delegate.selected_index_changed(ix, window, cx) { action(window, cx); } if scroll_to_index { self.scroll_to_item_index(ix); } } } pub fn select_next( &mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>, ) { let query = self.query(cx); if let Some(query) = self .delegate .select_history(Direction::Down, &query, window, cx) { self.set_query(&query, window, cx); return; } let count = self.delegate.match_count(); if count > 0 { let index = self.delegate.selected_index(); let ix = if index == count - 1 { 0 } else { index + 1 }; self.set_selected_index(ix, Some(Direction::Down), true, window, cx); cx.notify(); } } pub fn editor_move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) { self.select_previous(&Default::default(), window, cx); } fn select_previous( &mut self, _: &menu::SelectPrevious, window: &mut Window,