2950 lines · 111.7 KB · Rust
RawDownload
mod registrar; use crate::{    FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOption,    SearchOptions, SearchSource, SelectAllMatches, SelectNextMatch, SelectPreviousMatch,    ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord,    search_bar::{ActionButtonState, input_base_styles, render_action_button, render_text_input},};use any_vec::AnyVec;use anyhow::Context as _;use collections::HashMap;use editor::{    DisplayPoint, Editor, EditorSettings, VimFlavor,    actions::{Backtab, Tab},    vim_flavor,};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::sync::Arc;use zed_actions::{outline::ToggleOutline, workspace::CopyPath, workspace::CopyRelativePath}; use ui::{    BASE_REM_SIZE_IN_PX, IconButton, IconButtonShape, IconName, Tooltip, h_flex, prelude::*,    utils::SearchInputWidth,};use util::{ResultExt, paths::PathMatcher};use workspace::{    ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,    item::ItemHandle,    searchable::{        Direction, FilteredSearchRange, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle,    },}; pub use registrar::DivRegistrar;use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults}; 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>,    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_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>>,} impl BufferSearchBar {    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 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 query_column = input_base_styles(query_border)            .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 search_line = h_flex()            .w_full()            .gap_2()            .when(find_in_results, |el| {                el.child(Label::new("Find in results").color(Color::Hint))            })            .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()                    .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, |div| {                    div.child(h_flex().absolute().right_0().child(render_action_button(                        "buffer-search",                        IconName::Close,                        Default::default(),                        "Close Search Bar",                        &Dismiss,                        focus_handle.clone(),                    )))                    .w_full()                });        v_flex()            .id("buffer_search")            .gap_2()            .py(px(1.0))            .w_full()            .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);                }            }))            .on_action(cx.listener(|this, _: &CopyPath, window, cx| {                if let Some(active_searchable_item) = &mut this.active_searchable_item {                    active_searchable_item.relay_action(Box::new(CopyPath), window, cx);                }            }))            .on_action(cx.listener(|this, _: &CopyRelativePath, window, cx| {                if let Some(active_searchable_item) = &mut this.active_searchable_item {                    active_searchable_item.relay_action(Box::new(CopyRelativePath), window, cx);                }            }))            .when(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(case, |this| {                this.on_action(cx.listener(Self::toggle_case_sensitive))            })            .when(word, |this| {                this.on_action(cx.listener(Self::toggle_whole_word))            })            .when(regex, |this| {                this.on_action(cx.listener(Self::toggle_regex))            })            .when(selection, |this| {                this.on_action(cx.listener(Self::toggle_selection))            })            .child(search_line)            .children(query_error_line)            .children(replace_line)    }} impl Focusable for BufferSearchBar {    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {        self.query_editor.focus_handle(cx)    }} impl ToolbarItemView for BufferSearchBar {    fn contribute_context(&self, context: &mut KeyContext, _cx: &App) {        if !self.dismissed {            context.add("buffer_search_deployed");        }    }     fn set_active_pane_item(        &mut self,        item: Option<&dyn ItemHandle>,        window: &mut Window,        cx: &mut Context<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))        {            let this = cx.entity().downgrade();             self.active_searchable_item_subscription =                Some(searchable_item_handle.subscribe_to_search_events(                    window,                    cx,                    Box::new(move |search_event, window, cx| {                        if let Some(this) = this.upgrade() {

Lines 1–500 of 2950.