2901 lines · 112.6 KB · Rust
RawDownload
mod registrar; use crate::{    FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,    SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex,    ToggleReplace, ToggleSelection, ToggleWholeWord, search_bar::render_nav_button,};use any_vec::AnyVec;use anyhow::Context as _;use collections::HashMap;use editor::{    DisplayPoint, Editor, EditorElement, EditorSettings, EditorStyle,    actions::{Backtab, Tab},};use futures::channel::oneshot;use gpui::{    Action, App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable,    InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle,    Styled, Subscription, Task, TextStyle, Window, actions, div, impl_actions,};use language::{Language, LanguageRegistry};use project::{    search::SearchQuery,    search_history::{SearchHistory, SearchHistoryCursor},};use schemars::JsonSchema;use serde::Deserialize;use settings::Settings;use std::sync::Arc;use theme::ThemeSettings;use zed_actions::outline::ToggleOutline; use ui::{    BASE_REM_SIZE_IN_PX, IconButton, IconButtonShape, IconName, Tooltip, h_flex, prelude::*,    utils::SearchInputWidth,};use util::ResultExt;use workspace::{    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,    item::ItemHandle,    searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle},}; pub use registrar::DivRegistrar;use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults}; const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50; #[derive(PartialEq, Clone, Deserialize, JsonSchema)]#[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,} impl_actions!(buffer_search, [Deploy]); actions!(buffer_search, [DeployReplace, Dismiss, 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>,    active_searchable_item_subscription: Option<Subscription>,    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_contains_error: bool,    dismissed: bool,    search_history: SearchHistory,    search_history_cursor: SearchHistoryCursor,    replace_enabled: bool,    selection_search_enabled: bool,    scroll_handle: ScrollHandle,    editor_scroll_handle: ScrollHandle,    editor_needed_width: Pixels,    regex_language: Option<Arc<Language>>,} impl BufferSearchBar {    fn render_text_input(        &self,        editor: &Entity<Editor>,        color_override: Option<Color>,        cx: &mut Context<Self>,    ) -> impl IntoElement {        let (color, use_syntax) = if editor.read(cx).read_only(cx) {            (cx.theme().colors().text_disabled, false)        } else {            match color_override {                Some(color_override) => (color_override.color(cx), false),                None => (cx.theme().colors().text, true),            }        };         let settings = ThemeSettings::get_global(cx);        let text_style = TextStyle {            color,            font_family: settings.buffer_font.family.clone(),            font_features: settings.buffer_font.features.clone(),            font_fallbacks: settings.buffer_font.fallbacks.clone(),            font_size: rems(0.875).into(),            font_weight: settings.buffer_font.weight,            line_height: relative(1.3),            ..TextStyle::default()        };         let mut editor_style = EditorStyle {            background: cx.theme().colors().toolbar_background,            local_player: cx.theme().players().local(),            text: text_style,            ..EditorStyle::default()        };        if use_syntax {            editor_style.syntax = cx.theme().syntax().clone();        }         EditorElement::new(editor, editor_style)    }     pub fn query_editor_focused(&self) -> bool {        self.query_editor_focused    }} 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 {        if self.dismissed {            return div().id("search_bar");        }         let focus_handle = self.focus_handle(cx);         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 supported_options = self.supported_options(cx);         if self.query_editor.update(cx, |query_editor, _cx| {            query_editor.placeholder_text().is_none()        }) {            self.query_editor.update(cx, |editor, cx| {                editor.set_placeholder_text("Search…", cx);            });        }         self.replacement_editor.update(cx, |editor, cx| {            editor.set_placeholder_text("Replace with…", 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 && supported_options.replacement;        let in_replace = self.replacement_editor.focus_handle(cx).is_focused(window);         let mut key_context = KeyContext::new_with_defaults();        key_context.add("BufferSearchBar");        if in_replace {            key_context.add("in_replace");        }        let editor_border = if self.query_contains_error {            Color::Error.color(cx)        } else {            cx.theme().colors().border        };         let container_width = window.viewport_size().width;        let input_width = SearchInputWidth::calc_width(container_width);         let input_base_styles = || {            h_flex()                .min_w_32()                .w(input_width)                .h_8()                .pl_2()                .pr_1()                .py_1()                .border_1()                .border_color(editor_border)                .rounded_lg()        };         let search_line = h_flex()            .gap_2()            .when(supported_options.find_in_results, |el| {                el.child(Label::new("Find in results").color(Color::Hint))            })            .child(                input_base_styles()                    .id("editor-scroll")                    .track_scroll(&self.editor_scroll_handle)                    .child(self.render_text_input(&self.query_editor, color_override, cx))                    .when(!hide_inline_icons, |div| {                        div.child(                            h_flex()                                .gap_1()                                .children(supported_options.case.then(|| {                                    self.render_search_option_button(                                        SearchOptions::CASE_SENSITIVE,                                        focus_handle.clone(),                                        cx.listener(|this, _, window, cx| {                                            this.toggle_case_sensitive(                                                &ToggleCaseSensitive,                                                window,                                                cx,                                            )                                        }),                                    )                                }))                                .children(supported_options.word.then(|| {                                    self.render_search_option_button(                                        SearchOptions::WHOLE_WORD,                                        focus_handle.clone(),                                        cx.listener(|this, _, window, cx| {                                            this.toggle_whole_word(&ToggleWholeWord, window, cx)                                        }),                                    )                                }))                                .children(supported_options.regex.then(|| {                                    self.render_search_option_button(                                        SearchOptions::REGEX,                                        focus_handle.clone(),                                        cx.listener(|this, _, window, cx| {                                            this.toggle_regex(&ToggleRegex, window, cx)                                        }),                                    )                                })),                        )                    }),            )            .child(                h_flex()                    .gap_1()                    .min_w_64()                    .when(supported_options.replacement, |this| {                        this.child(                            IconButton::new(                                "buffer-search-bar-toggle-replace-button",                                IconName::Replace,                            )                            .style(ButtonStyle::Subtle)                            .shape(IconButtonShape::Square)                            .when(self.replace_enabled, |button| {                                button.style(ButtonStyle::Filled)                            })                            .on_click(cx.listener(|this, _: &ClickEvent, window, cx| {                                this.toggle_replace(&ToggleReplace, window, cx);                            }))                            .toggle_state(self.replace_enabled)                            .tooltip({                                let focus_handle = focus_handle.clone();                                move |window, cx| {                                    Tooltip::for_action_in(                                        "Toggle Replace",                                        &ToggleReplace,                                        &focus_handle,                                        window,                                        cx,                                    )                                }                            }),                        )                    })                    .when(supported_options.selection, |this| {                        this.child(                            IconButton::new(                                "buffer-search-bar-toggle-search-selection-button",                                IconName::SearchSelection,                            )                            .style(ButtonStyle::Subtle)                            .shape(IconButtonShape::Square)                            .when(self.selection_search_enabled, |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)                            .tooltip({                                let focus_handle = focus_handle.clone();                                move |window, cx| {                                    Tooltip::for_action_in(                                        "Toggle Search Selection",                                        &ToggleSelection,                                        &focus_handle,                                        window,                                        cx,                                    )                                }                            }),                        )                    })                    .when(!supported_options.find_in_results, |el| {                        el.child(                            IconButton::new("select-all", ui::IconName::SelectAll)                                .on_click(|_, window, cx| {                                    window.dispatch_action(SelectAllMatches.boxed_clone(), cx)                                })                                .shape(IconButtonShape::Square)                                .tooltip({                                    let focus_handle = focus_handle.clone();                                    move |window, cx| {                                        Tooltip::for_action_in(                                            "Select All Matches",                                            &SelectAllMatches,                                            &focus_handle,                                            window,                                            cx,                                        )                                    }                                }),                        )                        .child(                            h_flex()                                .pl_2()                                .ml_1()                                .border_l_1()                                .border_color(cx.theme().colors().border_variant)                                .child(render_nav_button(                                    ui::IconName::ChevronLeft,                                    self.active_match_index.is_some(),                                    "Select Previous Match",                                    &SelectPreviousMatch,                                    focus_handle.clone(),                                ))                                .child(render_nav_button(                                    ui::IconName::ChevronRight,                                    self.active_match_index.is_some(),                                    "Select Next Match",                                    &SelectNextMatch,                                    focus_handle.clone(),                                )),                        )                        .when(!narrow_mode, |this| {                            this.child(h_flex().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                                    },                                ),                            ))                        })                    })                    .when(supported_options.find_in_results, |el| {                        el.child(                            IconButton::new(SharedString::from("Close"), IconName::Close)                                .shape(IconButtonShape::Square)                                .tooltip(move |window, cx| {                                    Tooltip::for_action("Close Search Bar", &Dismiss, window, cx)                                })                                .on_click(cx.listener(|this, _: &ClickEvent, window, cx| {                                    this.dismiss(&Dismiss, window, cx)                                })),                        )                    }),            );         let replace_line = should_show_replace_input.then(|| {            h_flex()                .gap_2()                .child(input_base_styles().child(self.render_text_input(                    &self.replacement_editor,                    None,                    cx,                )))                .child(                    h_flex()                        .min_w_64()                        .gap_1()                        .child(                            IconButton::new("search-replace-next", ui::IconName::ReplaceNext)                                .shape(IconButtonShape::Square)                                .tooltip({                                    let focus_handle = focus_handle.clone();                                    move |window, cx| {                                        Tooltip::for_action_in(                                            "Replace Next Match",                                            &ReplaceNext,                                            &focus_handle,                                            window,                                            cx,                                        )                                    }                                })                                .on_click(cx.listener(|this, _, window, cx| {                                    this.replace_next(&ReplaceNext, window, cx)                                })),                        )                        .child(                            IconButton::new("search-replace-all", ui::IconName::ReplaceAll)                                .shape(IconButtonShape::Square)                                .tooltip({                                    let focus_handle = focus_handle.clone();                                    move |window, cx| {                                        Tooltip::for_action_in(                                            "Replace All Matches",                                            &ReplaceAll,                                            &focus_handle,                                            window,                                            cx,                                        )                                    }                                })                                .on_click(cx.listener(|this, _, window, cx| {                                    this.replace_all(&ReplaceAll, window, cx)                                })),                        ),                )        });         v_flex()            .id("buffer_search")            .gap_2()            .py(px(1.0))            .track_scroll(&self.scroll_handle)            .key_context(key_context)            .capture_action(cx.listener(Self::tab))            .capture_action(cx.listener(Self::backtab))            .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 {                    active_searchable_item.relay_action(Box::new(ToggleOutline), window, cx);                }            }))            .when(self.supported_options(cx).replacement, |this| {                this.on_action(cx.listener(Self::toggle_replace))                    .when(in_replace, |this| {                        this.on_action(cx.listener(Self::replace_next))                            .on_action(cx.listener(Self::replace_all))                    })            })            .when(self.supported_options(cx).case, |this| {                this.on_action(cx.listener(Self::toggle_case_sensitive))            })

Lines 1–500 of 2901.