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. Load more
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() {