mod registrar; use crate::{ search_bar::render_nav_button, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord,};use any_vec::AnyVec;use anyhow::Context as _;use collections::HashMap;use editor::{ actions::{Backtab, Tab}, DisplayPoint, Editor, EditorElement, EditorSettings, EditorStyle,};use futures::channel::oneshot;use gpui::{ actions, div, impl_actions, Action, App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, Styled, Subscription, Task, TextStyle, Window,};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::{ 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)]#[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().editor_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 2879. Load more
mod registrar; use crate::{ search_bar::render_nav_button, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord, }; use any_vec::AnyVec; use anyhow::Context as _; use collections::HashMap; use editor::{ actions::{Backtab, Tab}, DisplayPoint, Editor, EditorElement, EditorSettings, EditorStyle, }; use futures::channel::oneshot; use gpui::{ actions, div, impl_actions, Action, App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, Styled, Subscription, Task, TextStyle, Window, }; 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::{ 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)] #[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().editor_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)) })