2622 lines · 100.8 KB · Rust
RawDownload
mod registrar; use crate::{    search_bar::render_nav_button, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll,    ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch,    ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord,};use any_vec::AnyVec;use collections::HashMap;use editor::{    actions::{Tab, TabPrev},    DisplayPoint, Editor, EditorElement, EditorSettings, EditorStyle,};use futures::channel::oneshot;use gpui::{    actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusHandle,    FocusableView, Hsla, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _,    Render, ScrollHandle, Styled, Subscription, Task, TextStyle, View, ViewContext,    VisualContext as _, WindowContext,};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 ui::{    h_flex, prelude::*, utils::SearchInputWidth, IconButton, IconButtonShape, IconName, Tooltip,    BASE_REM_SIZE_IN_PX,};use util::ResultExt;use workspace::{    item::ItemHandle,    searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle},    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,}; pub use registrar::DivRegistrar;use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults}; const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50; #[derive(PartialEq, Clone, Deserialize, JsonSchema)]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, [Dismiss, FocusEditor]); impl Deploy {    pub fn find() -> Self {        Self {            focus: true,            replace_enabled: false,            selection_search_enabled: false,        }    }} pub enum Event {    UpdateLocation,} pub fn init(cx: &mut AppContext) {    cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace))        .detach();} pub struct BufferSearchBar {    query_editor: View<Editor>,    query_editor_focused: bool,    replacement_editor: View<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,} impl BufferSearchBar {    fn render_text_input(        &self,        editor: &View<Editor>,        color: Hsla,        cx: &ViewContext<Self>,    ) -> impl IntoElement {        let settings = ThemeSettings::get_global(cx);        let text_style = TextStyle {            color: if editor.read(cx).read_only(cx) {                cx.theme().colors().text_disabled            } else {                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),            ..Default::default()        };         EditorElement::new(            editor,            EditorStyle {                background: cx.theme().colors().editor_background,                local_player: cx.theme().players().local(),                text: text_style,                ..Default::default()            },        )    }     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, cx: &mut ViewContext<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 / cx.rem_size() < 340. / BASE_REM_SIZE_IN_PX;        let hide_inline_icons = self.editor_needed_width            > self.editor_scroll_handle.bounds().size.width - cx.rem_size() * 6.;         let supported_options = self.supported_options();         if self.query_editor.update(cx, |query_editor, cx| {            query_editor.placeholder_text(cx).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 text_color = Color::Default;        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 {                    text_color = 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(cx);         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 = cx.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()            .child(                input_base_styles()                    .id("editor-scroll")                    .track_scroll(&self.editor_scroll_handle)                    .child(self.render_text_input(&self.query_editor, text_color.color(cx), 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, _, cx| {                                            this.toggle_case_sensitive(&ToggleCaseSensitive, cx)                                        }),                                    )                                }))                                .children(supported_options.word.then(|| {                                    self.render_search_option_button(                                        SearchOptions::WHOLE_WORD,                                        focus_handle.clone(),                                        cx.listener(|this, _, cx| {                                            this.toggle_whole_word(&ToggleWholeWord, cx)                                        }),                                    )                                }))                                .children(supported_options.regex.then(|| {                                    self.render_search_option_button(                                        SearchOptions::REGEX,                                        focus_handle.clone(),                                        cx.listener(|this, _, cx| {                                            this.toggle_regex(&ToggleRegex, 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, cx| {                                this.toggle_replace(&ToggleReplace, cx);                            }))                            .toggle_state(self.replace_enabled)                            .tooltip({                                let focus_handle = focus_handle.clone();                                move |cx| {                                    Tooltip::for_action_in(                                        "Toggle Replace",                                        &ToggleReplace,                                        &focus_handle,                                        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, cx| {                                this.toggle_selection(&ToggleSelection, cx);                            }))                            .toggle_state(self.selection_search_enabled)                            .tooltip({                                let focus_handle = focus_handle.clone();                                move |cx| {                                    Tooltip::for_action_in(                                        "Toggle Search Selection",                                        &ToggleSelection,                                        &focus_handle,                                        cx,                                    )                                }                            }),                        )                    })                    .child(                        IconButton::new("select-all", ui::IconName::SelectAll)                            .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone()))                            .shape(IconButtonShape::Square)                            .tooltip({                                let focus_handle = focus_handle.clone();                                move |cx| {                                    Tooltip::for_action_in(                                        "Select All Matches",                                        &SelectAllMatches,                                        &focus_handle,                                        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",                                &SelectPrevMatch,                                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                                },                            ),                        ))                    }),            );         let replace_line = should_show_replace_input.then(|| {            h_flex()                .gap_2()                .child(input_base_styles().child(self.render_text_input(                    &self.replacement_editor,                    cx.theme().colors().text,                    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 |cx| {                                        Tooltip::for_action_in(                                            "Replace Next Match",                                            &ReplaceNext,                                            &focus_handle,                                            cx,                                        )                                    }                                })                                .on_click(                                    cx.listener(|this, _, cx| this.replace_next(&ReplaceNext, cx)),                                ),                        )                        .child(                            IconButton::new("search-replace-all", ui::IconName::ReplaceAll)                                .shape(IconButtonShape::Square)                                .tooltip({                                    let focus_handle = focus_handle.clone();                                    move |cx| {                                        Tooltip::for_action_in(                                            "Replace All Matches",                                            &ReplaceAll,                                            &focus_handle,                                            cx,                                        )                                    }                                })                                .on_click(                                    cx.listener(|this, _, cx| this.replace_all(&ReplaceAll, 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::tab_prev))            .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))            .when(self.supported_options().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().case, |this| {                this.on_action(cx.listener(Self::toggle_case_sensitive))            })            .when(self.supported_options().word, |this| {                this.on_action(cx.listener(Self::toggle_whole_word))            })            .when(self.supported_options().regex, |this| {                this.on_action(cx.listener(Self::toggle_regex))            })            .when(self.supported_options().selection, |this| {                this.on_action(cx.listener(Self::toggle_selection))            })            .child(                h_flex()                    .relative()                    .child(search_line.w_full())                    .when(!narrow_mode, |div| {                        div.child(                            h_flex().absolute().right_0().child(                                IconButton::new(SharedString::from("Close"), IconName::Close)                                    .shape(IconButtonShape::Square)                                    .tooltip(move |cx| {                                        Tooltip::for_action("Close Search Bar", &Dismiss, cx)                                    })                                    .on_click(cx.listener(|this, _: &ClickEvent, cx| {                                        this.dismiss(&Dismiss, cx)                                    })),                            ),                        )                    }),            )            .children(replace_line)    }} impl FocusableView for BufferSearchBar {    fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {        self.query_editor.focus_handle(cx)    }} impl ToolbarItemView for BufferSearchBar {    fn set_active_pane_item(        &mut self,        item: Option<&dyn ItemHandle>,        cx: &mut ViewContext<Self>,    ) -> ToolbarItemLocation {        cx.notify();        self.active_searchable_item_subscription.take();        self.active_searchable_item.take();         self.pending_search.take();         if let Some(searchable_item_handle) =            item.and_then(|item| item.to_searchable_item_handle(cx))

Lines 1–500 of 2622.