3533 lines · 133.1 KB · Rust
RawDownload
mod registrar; use crate::{    FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOption,    SearchOptions, SearchSource, SelectAllMatches, SelectNextMatch, SelectPreviousMatch,    ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord,    buffer_search::registrar::WithResultsOrExternalQuery,    search_bar::{        ActionButtonState, alignment_element, filter_search_results_input, input_base_styles,        render_action_button, render_text_input,    },};use any_vec::AnyVec;use collections::HashMap;use editor::{    DisplayPoint, Editor, EditorSettings, MultiBufferOffset,    actions::{Backtab, FoldAll, Tab, ToggleFoldAll, UnfoldAll},};use futures::channel::oneshot;use gpui::{    Action, App, ClickEvent, Context, Entity, EventEmitter, Focusable, InteractiveElement as _,    IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, Styled, Subscription, Task,    Window, actions, div,};use language::{Language, LanguageRegistry};use project::{    search::SearchQuery,    search_history::{SearchHistory, SearchHistoryCursor},};use schemars::JsonSchema;use serde::Deserialize;use settings::Settings;use std::{any::TypeId, sync::Arc};use zed_actions::{outline::ToggleOutline, workspace::CopyPath, workspace::CopyRelativePath}; use ui::{BASE_REM_SIZE_IN_PX, IconButtonShape, Tooltip, prelude::*, utils::SearchInputWidth};use util::{ResultExt, paths::PathMatcher};use workspace::{    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,    item::{ItemBufferKind, ItemHandle},    searchable::{        CollapseDirection, Direction, FilteredSearchRange, SearchEvent, SearchableItemHandle,        WeakSearchableItemHandle,    },}; pub use registrar::DivRegistrar;use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar}; const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50; /// Opens the buffer search interface with the specified configuration.#[derive(PartialEq, Clone, Deserialize, JsonSchema, Action)]#[action(namespace = buffer_search)]#[serde(deny_unknown_fields)]pub struct Deploy {    #[serde(default = "util::serde::default_true")]    pub focus: bool,    #[serde(default)]    pub replace_enabled: bool,    #[serde(default)]    pub selection_search_enabled: bool,} actions!(    buffer_search,    [        /// Deploys the search and replace interface.        DeployReplace,        /// Dismisses the search bar.        Dismiss,        /// Focuses back on the editor.        FocusEditor    ]); impl Deploy {    pub fn find() -> Self {        Self {            focus: true,            replace_enabled: false,            selection_search_enabled: false,        }    }     pub fn replace() -> Self {        Self {            focus: true,            replace_enabled: true,            selection_search_enabled: false,        }    }} pub enum Event {    UpdateLocation,} pub fn init(cx: &mut App) {    cx.observe_new(|workspace: &mut Workspace, _, _| BufferSearchBar::register(workspace))        .detach();} pub struct BufferSearchBar {    query_editor: Entity<Editor>,    query_editor_focused: bool,    replacement_editor: Entity<Editor>,    replacement_editor_focused: bool,    active_searchable_item: Option<Box<dyn SearchableItemHandle>>,    active_match_index: Option<usize>,    #[cfg(target_os = "macos")]    active_searchable_item_subscriptions: Option<[Subscription; 2]>,    #[cfg(not(target_os = "macos"))]    active_searchable_item_subscriptions: Option<Subscription>,    #[cfg(target_os = "macos")]    pending_external_query: Option<(String, SearchOptions)>,    active_search: Option<Arc<SearchQuery>>,    searchable_items_with_matches: HashMap<Box<dyn WeakSearchableItemHandle>, AnyVec<dyn Send>>,    pending_search: Option<Task<()>>,    search_options: SearchOptions,    default_options: SearchOptions,    configured_options: SearchOptions,    query_error: Option<String>,    dismissed: bool,    search_history: SearchHistory,    search_history_cursor: SearchHistoryCursor,    replace_enabled: bool,    selection_search_enabled: Option<FilteredSearchRange>,    scroll_handle: ScrollHandle,    editor_scroll_handle: ScrollHandle,    editor_needed_width: Pixels,    regex_language: Option<Arc<Language>>,    is_collapsed: bool,} impl EventEmitter<Event> for BufferSearchBar {}impl EventEmitter<workspace::ToolbarItemEvent> for BufferSearchBar {}impl Render for BufferSearchBar {    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        let focus_handle = self.focus_handle(cx);         let collapse_expand_button = if self.needs_expand_collapse_option(cx) {            let query_editor_focus = self.query_editor.focus_handle(cx);             let (icon, label, tooltip_label) = if self.is_collapsed {                (                    IconName::ChevronUpDown,                    "Expand All",                    "Expand All Search Results",                )            } else {                (                    IconName::ChevronDownUp,                    "Collapse All",                    "Collapse All Search Results",                )            };             if self.dismissed {                let button = Button::new("multibuffer-collapse-expand-empty", label)                    .icon_position(IconPosition::Start)                    .icon(icon)                    .tooltip(move |_, cx| {                        Tooltip::for_action_in(                            tooltip_label,                            &ToggleFoldAll,                            &query_editor_focus.clone(),                            cx,                        )                    })                    .on_click(|_event, window, cx| {                        window.dispatch_action(ToggleFoldAll.boxed_clone(), cx)                    })                    .into_any_element();                 return button;            }             Some(                IconButton::new("multibuffer-collapse-expand", icon)                    .shape(IconButtonShape::Square)                    .tooltip(move |_, cx| {                        Tooltip::for_action_in(                            tooltip_label,                            &ToggleFoldAll,                            &query_editor_focus,                            cx,                        )                    })                    .on_click(|_event, window, cx| {                        window.dispatch_action(ToggleFoldAll.boxed_clone(), cx)                    })                    .into_any_element(),            )        } else {            None        };         let narrow_mode =            self.scroll_handle.bounds().size.width / window.rem_size() < 340. / BASE_REM_SIZE_IN_PX;        let hide_inline_icons = self.editor_needed_width            > self.editor_scroll_handle.bounds().size.width - window.rem_size() * 6.;         let workspace::searchable::SearchOptions {            case,            word,            regex,            replacement,            selection,            find_in_results,        } = self.supported_options(cx);         self.query_editor.update(cx, |query_editor, cx| {            if query_editor.placeholder_text(cx).is_none() {                query_editor.set_placeholder_text("Search…", window, cx);            }        });         self.replacement_editor.update(cx, |editor, cx| {            editor.set_placeholder_text("Replace with…", window, cx);        });         let mut color_override = None;        let match_text = self            .active_searchable_item            .as_ref()            .and_then(|searchable_item| {                if self.query(cx).is_empty() {                    return None;                }                let matches_count = self                    .searchable_items_with_matches                    .get(&searchable_item.downgrade())                    .map(AnyVec::len)                    .unwrap_or(0);                if let Some(match_ix) = self.active_match_index {                    Some(format!("{}/{}", match_ix + 1, matches_count))                } else {                    color_override = Some(Color::Error); // No matches found                    None                }            })            .unwrap_or_else(|| "0/0".to_string());        let should_show_replace_input = self.replace_enabled && replacement;        let in_replace = self.replacement_editor.focus_handle(cx).is_focused(window);         let theme_colors = cx.theme().colors();        let query_border = if self.query_error.is_some() {            Color::Error.color(cx)        } else {            theme_colors.border        };        let replacement_border = theme_colors.border;         let container_width = window.viewport_size().width;        let input_width = SearchInputWidth::calc_width(container_width);         let input_base_styles =            |border_color| input_base_styles(border_color, |div| div.w(input_width));         let input_style = if find_in_results {            filter_search_results_input(query_border, |div| div.w(input_width), cx)        } else {            input_base_styles(query_border)        };         let query_column = input_style            .id("editor-scroll")            .track_scroll(&self.editor_scroll_handle)            .child(render_text_input(&self.query_editor, color_override, cx))            .when(!hide_inline_icons, |div| {                div.child(                    h_flex()                        .gap_1()                        .when(case, |div| {                            div.child(SearchOption::CaseSensitive.as_button(                                self.search_options,                                SearchSource::Buffer,                                focus_handle.clone(),                            ))                        })                        .when(word, |div| {                            div.child(SearchOption::WholeWord.as_button(                                self.search_options,                                SearchSource::Buffer,                                focus_handle.clone(),                            ))                        })                        .when(regex, |div| {                            div.child(SearchOption::Regex.as_button(                                self.search_options,                                SearchSource::Buffer,                                focus_handle.clone(),                            ))                        }),                )            });         let mode_column = h_flex()            .gap_1()            .min_w_64()            .when(replacement, |this| {                this.child(render_action_button(                    "buffer-search-bar-toggle",                    IconName::Replace,                    self.replace_enabled.then_some(ActionButtonState::Toggled),                    "Toggle Replace",                    &ToggleReplace,                    focus_handle.clone(),                ))            })            .when(selection, |this| {                this.child(                    IconButton::new(                        "buffer-search-bar-toggle-search-selection-button",                        IconName::Quote,                    )                    .style(ButtonStyle::Subtle)                    .shape(IconButtonShape::Square)                    .when(self.selection_search_enabled.is_some(), |button| {                        button.style(ButtonStyle::Filled)                    })                    .on_click(cx.listener(|this, _: &ClickEvent, window, cx| {                        this.toggle_selection(&ToggleSelection, window, cx);                    }))                    .toggle_state(self.selection_search_enabled.is_some())                    .tooltip({                        let focus_handle = focus_handle.clone();                        move |_window, cx| {                            Tooltip::for_action_in(                                "Toggle Search Selection",                                &ToggleSelection,                                &focus_handle,                                cx,                            )                        }                    }),                )            })            .when(!find_in_results, |el| {                let query_focus = self.query_editor.focus_handle(cx);                let matches_column = h_flex()                    .pl_2()                    .ml_2()                    .border_l_1()                    .border_color(theme_colors.border_variant)                    .child(render_action_button(                        "buffer-search-nav-button",                        ui::IconName::ChevronLeft,                        self.active_match_index                            .is_none()                            .then_some(ActionButtonState::Disabled),                        "Select Previous Match",                        &SelectPreviousMatch,                        query_focus.clone(),                    ))                    .child(render_action_button(                        "buffer-search-nav-button",                        ui::IconName::ChevronRight,                        self.active_match_index                            .is_none()                            .then_some(ActionButtonState::Disabled),                        "Select Next Match",                        &SelectNextMatch,                        query_focus.clone(),                    ))                    .when(!narrow_mode, |this| {                        this.child(div().ml_2().min_w(rems_from_px(40.)).child(                            Label::new(match_text).size(LabelSize::Small).color(                                if self.active_match_index.is_some() {                                    Color::Default                                } else {                                    Color::Disabled                                },                            ),                        ))                    });                 el.child(render_action_button(                    "buffer-search-nav-button",                    IconName::SelectAll,                    Default::default(),                    "Select All Matches",                    &SelectAllMatches,                    query_focus,                ))                .child(matches_column)            })            .when(find_in_results, |el| {                el.child(render_action_button(                    "buffer-search",                    IconName::Close,                    Default::default(),                    "Close Search Bar",                    &Dismiss,                    focus_handle.clone(),                ))            });         let has_collapse_button = collapse_expand_button.is_some();         let search_line = h_flex()            .w_full()            .gap_2()            .when(find_in_results, |el| el.child(alignment_element()))            .when(!find_in_results && has_collapse_button, |el| {                el.pl_0p5().child(collapse_expand_button.expect("button"))            })            .child(query_column)            .child(mode_column);         let replace_line =            should_show_replace_input.then(|| {                let replace_column = input_base_styles(replacement_border)                    .child(render_text_input(&self.replacement_editor, None, cx));                let focus_handle = self.replacement_editor.read(cx).focus_handle(cx);                 let replace_actions = h_flex()                    .min_w_64()                    .gap_1()                    .child(render_action_button(                        "buffer-search-replace-button",                        IconName::ReplaceNext,                        Default::default(),                        "Replace Next Match",                        &ReplaceNext,                        focus_handle.clone(),                    ))                    .child(render_action_button(                        "buffer-search-replace-button",                        IconName::ReplaceAll,                        Default::default(),                        "Replace All Matches",                        &ReplaceAll,                        focus_handle,                    ));                 h_flex()                    .w_full()                    .gap_2()                    .when(has_collapse_button, |this| this.child(alignment_element()))                    .child(replace_column)                    .child(replace_actions)            });         let mut key_context = KeyContext::new_with_defaults();        key_context.add("BufferSearchBar");        if in_replace {            key_context.add("in_replace");        }         let query_error_line = self.query_error.as_ref().map(|error| {            Label::new(error)                .size(LabelSize::Small)                .color(Color::Error)                .mt_neg_1()                .ml_2()        });         let search_line =            h_flex()                .relative()                .child(search_line)                .when(!narrow_mode && !find_in_results, |this| {                    this.child(                        h_flex()                            .absolute()                            .right_0()                            .when(has_collapse_button, |this| {                                this.pr_2()                                    .border_r_1()                                    .border_color(cx.theme().colors().border_variant)                            })                            .child(render_action_button(                                "buffer-search",                                IconName::Close,                                Default::default(),                                "Close Search Bar",                                &Dismiss,                                focus_handle.clone(),                            )),                    )                });         v_flex()            .id("buffer_search")            .gap_2()            .w_full()            .track_scroll(&self.scroll_handle)            .key_context(key_context)            .capture_action(cx.listener(Self::tab))            .capture_action(cx.listener(Self::backtab))            .capture_action(cx.listener(Self::toggle_fold_all))            .on_action(cx.listener(Self::previous_history_query))            .on_action(cx.listener(Self::next_history_query))            .on_action(cx.listener(Self::dismiss))            .on_action(cx.listener(Self::select_next_match))            .on_action(cx.listener(Self::select_prev_match))            .on_action(cx.listener(|this, _: &ToggleOutline, window, cx| {                if let Some(active_searchable_item) = &mut this.active_searchable_item {

Lines 1–500 of 3533.