3807 lines · 145.9 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, HistoryNavigationDirection, alignment_element,        filter_search_results_input, input_base_styles, render_action_button, render_text_input,        should_navigate_history,    },};use any_vec::AnyVec;use collections::HashMap;use editor::{    Editor, EditorSettings, MultiBufferOffset, SplittableEditor, ToggleSplitDiff,    actions::{Backtab, FoldAll, Tab, ToggleFoldAll, UnfoldAll},    scroll::Autoscroll,};use futures::channel::oneshot;use gpui::{    Action as _, App, ClickEvent, Context, Entity, EventEmitter, Focusable,    InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle,    Styled, Subscription, Task, WeakEntity, Window, div,};use language::{Language, LanguageRegistry};use project::{    search::SearchQuery,    search_history::{SearchHistory, SearchHistoryCursor},}; use fs::Fs;use settings::{DiffViewStyle, Settings, update_settings_file};use std::{any::TypeId, sync::Arc};use zed_actions::{    OpenSettingsAt, outline::ToggleOutline, workspace::CopyPath, workspace::CopyRelativePath,}; use ui::{    BASE_REM_SIZE_IN_PX, IconButtonShape, PlatformStyle, TextSize, Tooltip, prelude::*,    render_modifiers, utils::SearchInputWidth,};use util::{ResultExt, paths::PathMatcher};use workspace::{    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,    item::{ItemBufferKind, ItemHandle},    searchable::{        Direction, FilteredSearchRange, SearchEvent, SearchToken, SearchableItemHandle,        WeakSearchableItemHandle,    },}; pub use registrar::{DivRegistrar, register_pane_search_actions};use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar}; const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50; pub use zed_actions::buffer_search::{Deploy, DeployReplace, Dismiss, FocusEditor}; pub enum Event {    UpdateLocation,    Dismissed,} 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>, SearchToken)>,    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,    regex_language: Option<Arc<Language>>,    splittable_editor: Option<WeakEntity<SplittableEditor>>,    _splittable_editor_subscription: Option<Subscription>,} 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 has_splittable_editor = self.splittable_editor.is_some();        let split_buttons = if has_splittable_editor {            self.splittable_editor                .as_ref()                .and_then(|weak| weak.upgrade())                .map(|splittable_editor| {                    let editor_ref = splittable_editor.read(cx);                    let diff_view_style = editor_ref.diff_view_style();                     let is_split_set = diff_view_style == DiffViewStyle::Split;                    let is_split_active = editor_ref.is_split();                    let min_columns =                        EditorSettings::get_global(cx).minimum_split_diff_width as u32;                     let split_icon = if is_split_set && !is_split_active {                        IconName::DiffSplitAuto                    } else {                        IconName::DiffSplit                    };                     h_flex()                        .gap_1()                        .child(                            IconButton::new("diff-unified", IconName::DiffUnified)                                .icon_size(IconSize::Small)                                .toggle_state(diff_view_style == DiffViewStyle::Unified)                                .tooltip(Tooltip::text("Unified"))                                .on_click({                                    let splittable_editor = splittable_editor.downgrade();                                    move |_, window, cx| {                                        update_settings_file(                                            <dyn Fs>::global(cx),                                            cx,                                            |settings, _| {                                                settings.editor.diff_view_style =                                                    Some(DiffViewStyle::Unified);                                            },                                        );                                        if diff_view_style == DiffViewStyle::Split {                                            splittable_editor                                                .update(cx, |editor, cx| {                                                    editor.toggle_split(                                                        &ToggleSplitDiff,                                                        window,                                                        cx,                                                    );                                                })                                                .ok();                                        }                                    }                                }),                        )                        .child(                            IconButton::new("diff-split", split_icon)                                .toggle_state(diff_view_style == DiffViewStyle::Split)                                .icon_size(IconSize::Small)                                .tooltip(Tooltip::element(move |_, cx| {                                    let message = if is_split_set && !is_split_active {                                        format!("Split when wider than {} columns", min_columns)                                            .into()                                    } else {                                        SharedString::from("Split")                                    };                                     v_flex()                                        .child(message)                                        .child(                                            h_flex()                                                .gap_0p5()                                                .text_ui_sm(cx)                                                .text_color(Color::Muted.color(cx))                                                .children(render_modifiers(                                                    &gpui::Modifiers::secondary_key(),                                                    PlatformStyle::platform(),                                                    None,                                                    Some(TextSize::Small.rems(cx).into()),                                                    false,                                                ))                                                .child("click to change min width"),                                        )                                        .into_any()                                }))                                .on_click({                                    let splittable_editor = splittable_editor.downgrade();                                    move |_, window, cx| {                                        if window.modifiers().secondary() {                                            window.dispatch_action(                                                OpenSettingsAt {                                                    path: "minimum_split_diff_width".to_string(),                                                }                                                .boxed_clone(),                                                cx,                                            );                                        } else {                                            update_settings_file(                                                <dyn Fs>::global(cx),                                                cx,                                                |settings, _| {                                                    settings.editor.diff_view_style =                                                        Some(DiffViewStyle::Split);                                                },                                            );                                            if diff_view_style == DiffViewStyle::Unified {                                                splittable_editor                                                    .update(cx, |editor, cx| {                                                        editor.toggle_split(                                                            &ToggleSplitDiff,                                                            window,                                                            cx,                                                        );                                                    })                                                    .ok();                                            }                                        }                                    }                                }),                        )                })        } else {            None        };         let collapse_expand_button = if self.needs_expand_collapse_option(cx) {            let query_editor_focus = self.query_editor.focus_handle(cx);             let is_collapsed = self                .active_searchable_item                .as_ref()                .and_then(|item| item.act_as_type(TypeId::of::<Editor>(), cx))                .and_then(|item| item.downcast::<Editor>().ok())                .map(|editor: Entity<Editor>| editor.read(cx).has_any_buffer_folded(cx))                .unwrap_or_default();            let (icon, tooltip_label) = if is_collapsed {                (IconName::ChevronUpDown, "Expand All Files")            } else {                (IconName::ChevronDownUp, "Collapse All Files")            };             let collapse_expand_icon_button = |id| {                IconButton::new(id, icon)                    .icon_size(IconSize::Small)                    .tooltip(move |_, cx| {                        Tooltip::for_action_in(                            tooltip_label,                            &ToggleFoldAll,                            &query_editor_focus,                            cx,                        )                    })                    .on_click(cx.listener(|this, _: &ClickEvent, window, cx| {                        this.toggle_fold_all(&ToggleFoldAll, window, cx);                    }))            };             if self.dismissed {                return h_flex()                    .pl_0p5()                    .gap_1()                    .child(collapse_expand_icon_button(                        "multibuffer-collapse-expand-empty",                    ))                    .when(has_splittable_editor, |this| this.children(split_buttons))                    .into_any_element();            }             Some(                h_flex()                    .gap_1()                    .child(collapse_expand_icon_button("multibuffer-collapse-expand"))                    .children(split_buttons)                    .into_any_element(),            )        } else {            None        };         let narrow_mode =            self.scroll_handle.bounds().size.width / window.rem_size() < 340. / BASE_REM_SIZE_IN_PX;         let workspace::searchable::SearchOptions {            case,            word,            regex,            replacement,            selection,            select_all,            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(|(matches, _)| matches.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            .child(div().flex_1().min_w_0().py_1().child(render_text_input(                &self.query_editor,                color_override,                cx,            )))            .child(                h_flex()                    .flex_none()                    .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.when(select_all, |el| {                    el.child(render_action_button(                        "buffer-search-nav-button",                        IconName::SelectAll,                        Default::default(),                        "Select All Matches",                        &SelectAllMatches,                        query_focus.clone(),                    ))                })                .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(|| {

Lines 1–500 of 3807.