1939 lines · 66.8 KB · Rust
RawDownload
use anyhow::Result;use gpui::Action;use gpui::{    AnyElement, App, Bounds, ClickEvent, Context, DismissEvent, EventEmitter, FocusHandle,    Focusable, ListSizingBehavior, ListState, MouseButton, MouseUpEvent, Pixels, 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 ui::{    Checkbox, ContextMenu, Divider, DocumentationAside, KeyBinding, PopoverMenuHandle, Tooltip,    prelude::*,};use ui_input::ErasedEditorEvent;use util::ResultExt;use workspace::ModalView;use zed_actions::editor::{MoveDown, MoveUp}; mod footer;mod head;pub mod highlighted_match_with_paths;pub mod parts;mod persistence;pub mod popover_menu;mod preview;mod render;mod shape; use crate::shape::RelativeHeight;use crate::shape::RelativeWidth;pub use footer::PickerAction;pub use language::{HighlightedText, HighlightedTextBuilder};pub use preview::Layout as PreviewLayout;pub use preview::MatchLocation;pub use preview::Preview;pub use preview::PreviewBackend;pub use preview::PreviewSource;pub use preview::Update as PreviewUpdate;pub use ui_input::ErasedEditor; pub const DEFAULT_MODAL_WIDTH: Rems = Rems(34.0);pub const DEFAULT_MODAL_MAX_HEIGHT: Rems = Rems(24.0); enum ElementContainer {    List(ListState),    UniformList(UniformListScrollHandle),} pub enum Direction {    Up,    Down,} #[derive(Clone, Copy, Debug, PartialEq, Eq)]pub enum ScrollBehavior {    RevealSelected,    PreserveOffset,} actions!(    picker,    [        /// Confirms the selected completion in the picker.        ConfirmCompletion,        /// Toggles the preview between hidden and visible.        TogglePreview,        /// Shows the preview to the right of the results.        SetPreviewRight,        /// Shows the preview below the results.        SetPreviewBelow,        /// Hides the preview.        SetPreviewHidden,        /// Opens the footer's actions menu.        ToggleActionsMenu,        /// Toggles multi-select mode, in which clicking items adds them to        /// the selection instead of opening them        ToggleMultiSelect,        /// Toggles the current item in the multi-selection and advances to        /// the next item, starting multi-select mode if it isn't already        /// active        MultiSelectNext,    ]); /// 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<()>>,} #[derive(Clone, Copy)]enum Presentation {    /// A self-contained modal: draws its own elevated background and dismisses    /// when it loses focus. May optionally be resized (persisting its size);    /// resizing only makes sense for modals.    Modal { resizable: bool },    /// A popover attached to a menu/trigger: draws its own elevated background    /// and dismisses when it loses focus, but is never resizable.    Popover,    /// Embedded inside a larger container (e.g. another modal) that provides its    /// own chrome and handles dismissal.    Embedded,} /// The default size for a given preview layout. With the preview hidden the/// picker uses its standard size; showing a preview expands it to the larger/// "telescope" size so the results pane isn't cramped beside the preview.fn default_shape_for_layout(hidden: shape::Centered, layout: preview::Layout) -> shape::Centered {    match layout {        preview::Layout::Hidden => hidden,        preview::Layout::Right | preview::Layout::Below => shape::Centered::default(),    }} pub struct Picker<D: PickerDelegate> {    pub delegate: D,    element_container: ElementContainer,    head: Head,    preview: Option<Preview>,    pending_update_matches: Option<PendingUpdateMatches>,    confirm_on_update: Option<bool>,    select_instead_of_open: bool,    shape: shape::Shape,    /// The size the picker opens at (and resets to). Defaults depend on whether    /// the picker has a preview; see [`Picker::initial_width`] / [`Picker::max_height`].    default_shape: shape::Centered,    size_bounds: shape::SizeBounds,    /// An external control to display a scrollbar in the `Picker`.    show_scrollbar: bool,    /// How the picker is presented, which controls its chrome and behavior.    presentation: Presentation,    /// 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>>>>,    shape_loaded_from_persistence: bool,    /// Handle for the default footer's Actions popover menu. Used to keep the    /// picker open while that menu has focus.    actions_menu_handle: PopoverMenuHandle<ContextMenu>,    reopenable: bool,} #[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;     /// Name of the picker, this is the key for serialization. We could use the    /// typename of the delegate but then a rename would break persistence.    fn name() -> &'static str;    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(        &self,        _ix: usize,        _window: &mut Window,        _cx: &mut Context<Picker<Self>>,    ) -> bool {        true    }    fn select_on_hover(&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>>);    /// Whether this delegate supports selecting multiple items at once. When    /// `true`, the delegate owns the multi-selection state; the picker only    /// drives the generic UX (toggle action, indicator, click routing).    fn supports_multi_select(&self) -> bool {        false    }    /// Whether the item at `ix` is part of the current multi-selection.    fn is_item_selected(&self, _ix: usize) -> bool {        false    }    /// Toggle whether the item at `ix` is part of the multi-selection. Items    /// that cannot participate in multi-selection (e.g. non-file entries)    /// should leave the selection unchanged.    fn toggle_item_selected(        &mut self,        _ix: usize,        _window: &mut Window,        _cx: &mut Context<Picker<Self>>,    ) {    }    /// Number of items currently in the multi-selection.    fn selected_item_count(&self) -> usize {        0    }    /// Clear the multi-selection.    fn clear_selection(&mut self, _cx: &mut Context<Picker<Self>>) {}    /// Open every item in the multi-selection. Called on confirm when the    /// selection is non-empty; implementations should clear the selection.    fn confirm_multi(        &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    }    /// Called when `SelectChild` fires (e.g. shift-right-arrow). Return `Some(query)`    /// to step into the currently selected item (e.g. a directory); the picker    /// will set the query and refresh matches.    fn select_child(        &mut self,        _window: &mut Window,        _cx: &mut Context<Picker<Self>>,    ) -> Option<String> {        None    }     /// Called when `SelectParent` fires (e.g. shift-left-arrow). Return `Some(query)`    /// to step back to the parent; the picker will set the query and refresh    /// matches.    fn select_parent(        &mut self,        _window: &mut Window,        _cx: &mut Context<Picker<Self>>,    ) -> Option<String> {        None    }     fn editor_position(&self) -> PickerEditorPosition {        PickerEditorPosition::default()    }     /// Prevent closing the modal on clicking in a popover menu that portrudes out    /// This is already set by the Actions menu from the picker, this is here to    /// support extra menus added by the delegate.    fn has_another_open_menu(&self, _window: &Window, _cx: &App) -> bool {        false    }     /// An optional control rendered at the trailing edge of the search bar, e.g.    /// a filter toggle. Returning `Some` is the easy way to add such a control;    /// for full control over the search bar, override [`Self::render_editor`].    fn searchbar_trailer(        &self,        _window: &mut Window,        _cx: &mut Context<Picker<Self>>,    ) -> Option<AnyElement> {        None    }     /// Overrides the search bar entirely. Most delegates should return `None`    /// to get the picker-rendered default (which includes    /// [`Self::searchbar_trailer`] and the multi-select toggle); override for    /// full control over the search bar.    fn render_editor(        &self,        _editor: &Arc<dyn ErasedEditor>,        _window: &mut Window,        _cx: &mut Context<Picker<Self>>,    ) -> Option<Div> {        None    }     fn try_get_preview_data_for_match(&self, _cx: &App) -> Option<PreviewUpdate> {        None    }     /// Called on the delegate when opening a preview to the side. Delegates can    /// then change how much space they use for rendering the match    fn preview_layout_changed(&mut self, _layout_is_horizontal: bool) {}     fn render_match(        &self,        ix: usize,        selected: bool,        window: &mut Window,        cx: &mut Context<Picker<Self>>,    ) -> Option<Self::ListItem>;     fn render_match_with_checkbox(        &self,        _ix: usize,        _selected: bool,        _checkbox: AnyElement,        _window: &mut Window,        _cx: &mut Context<Picker<Self>>,    ) -> Option<Self::ListItem> {        None    }     fn render_header(        &self,        _window: &mut Window,        _: &mut Context<Picker<Self>>,    ) -> Option<AnyElement> {        None    }     /// Overrides the picker's footer.    ///    /// Note there normally isn't a footer unless this is set or the picker has    /// a preview. If the picker has a preview add actions to it using picker_actions.    fn render_footer(        &self,        _window: &mut Window,        _: &mut Context<Picker<Self>>,    ) -> Option<AnyElement> {        None    }     /// Make this non-empty to have an `Actions` menu show up in the footer    fn actions_menu(        &self,        _window: &mut Window,        _cx: &mut Context<Picker<Self>>,    ) -> Vec<footer::PickerAction> {        Vec::new()    }     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, None, window, cx)    }     /// A picker similar to [`uniform_list()`](Self::uniform_list) however this picker has a    /// preview window where it shows extra information.    pub fn uniform_list_with_preview(        delegate: D,        preview: Arc<dyn PreviewBackend>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let head = Head::editor(            delegate.placeholder_text(window, cx),            Self::on_input_editor_event,            window,            cx,        );         let preview = Preview::new(preview);        Self::new(            delegate,            ContainerKind::UniformList,            head,            Some(preview),            window,            cx,        )    }     /// A picker similar to [`list()`](Self::list) (variable-height rows) but with    /// a preview window. Use this instead of [`uniform_list_with_preview()`](Self::uniform_list_with_preview)    /// when [`PickerDelegate::render_match`] can return rows of different heights    /// (e.g. section headers and separators interleaved with matches).    pub fn list_with_preview(        delegate: D,        preview: Arc<dyn PreviewBackend>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let head = Head::editor(            delegate.placeholder_text(window, cx),

Lines 1–500 of 1939.