3656 lines · 140.3 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::{    Editor, EditorSettings, MultiBufferOffset, SplittableEditor, ToggleSplitDiff,    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,    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::{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 is_split = splittable_editor.read(cx).is_split();                    let focus_handle = splittable_editor.focus_handle(cx);                    h_flex()                        .gap_1()                        .child(                            IconButton::new("diff-unified", IconName::DiffUnified)                                .shape(IconButtonShape::Square)                                .toggle_state(!is_split)                                .tooltip(Tooltip::element(move |_, cx| {                                    v_flex()                                        .gap_1()                                        .child(Label::new("Unified"))                                        .child(                                            h_flex()                                                .gap_0p5()                                                .text_sm()                                                .text_color(Color::Muted.color(cx))                                                .children(render_modifiers(                                                    &gpui::Modifiers::secondary_key(),                                                    PlatformStyle::platform(),                                                    None,                                                    Some(TextSize::Default.rems(cx).into()),                                                    false,                                                ))                                                .child("click to set as default"),                                        )                                        .into_any()                                }))                                .on_click({                                    let focus_handle = focus_handle.clone();                                    move |_, window, cx| {                                        if window.modifiers().secondary() {                                            update_settings_file(                                                <dyn Fs>::global(cx),                                                cx,                                                |settings, _| {                                                    settings.editor.diff_view_style =                                                        Some(DiffViewStyle::Unified);                                                },                                            );                                        }                                        if is_split {                                            focus_handle.focus(window, cx);                                            window                                                .dispatch_action(ToggleSplitDiff.boxed_clone(), cx);                                        }                                    }                                }),                        )                        .child(                            IconButton::new("diff-split", IconName::DiffSplit)                                .shape(IconButtonShape::Square)                                .toggle_state(is_split)                                .tooltip(Tooltip::element(move |_, cx| {                                    v_flex()                                        .gap_1()                                        .child(Label::new("Split"))                                        .child(                                            h_flex()                                                .gap_0p5()                                                .text_sm()                                                .text_color(Color::Muted.color(cx))                                                .children(render_modifiers(                                                    &gpui::Modifiers::secondary_key(),                                                    PlatformStyle::platform(),                                                    None,                                                    Some(TextSize::Default.rems(cx).into()),                                                    false,                                                ))                                                .child("click to set as default"),                                        )                                        .into_any()                                }))                                .on_click({                                    move |_, window, cx| {                                        if window.modifiers().secondary() {                                            update_settings_file(                                                <dyn Fs>::global(cx),                                                cx,                                                |settings, _| {                                                    settings.editor.diff_view_style =                                                        Some(DiffViewStyle::Split);                                                },                                            );                                        }                                        if !is_split {                                            focus_handle.focus(window, cx);                                            window                                                .dispatch_action(ToggleSplitDiff.boxed_clone(), cx);                                        }                                    }                                }),                        )                })        } 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)                    .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)                    })            };             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,            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(px(0.))                    .overflow_hidden()                    .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.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,

Lines 1–500 of 3656.