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 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)]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() .px_2() .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.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_2() .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() .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)) { let this = cx.view().downgrade(); self.active_searchable_item_subscription = Some(searchable_item_handle.subscribe_to_search_events( cx, Box::new(move |search_event, cx| { if let Some(this) = this.upgrade() { this.update(cx, |this, cx| {Lines 1–500 of 2613. Load more
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 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)] 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() .px_2() .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.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_2() .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() .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)) { let this = cx.view().downgrade(); self.active_searchable_item_subscription = Some(searchable_item_handle.subscribe_to_search_events( cx, Box::new(move |search_event, cx| { if let Some(this) = this.upgrade() { this.update(cx, |this, cx| {