From ab96155d6a968e73f1368a5af45888e70796aab2 Mon Sep 17 00:00:00 2001 From: Dino Date: Thu, 27 Nov 2025 11:00:38 +0000 Subject: [PATCH] buffer_search: Fix replace buttons not working if search bar is not focused (#43569) Update the way that both `search::buffer_search::BufferSearchBar.replace_next` and `search::buffer_search::BufferSearchBar.replace_all` are registered as listeners, so that we don't require the replacement editor to be focused in order for these listeners to be active, only requiring the replacement mode to be active in the buffer search bar. This means that, even if the user is focused on the buffer editor, if the "Replace Next Match" or "Replace All Matches" buttons are clicked, the replacement will be performed. Closes #42471 Release Notes: - Fixed issue with buffer search bar where the replacement buttons ("Replace Next Match" & "Replace All Matches") wouldn't work if search bar was not focused --- crates/search/src/buffer_search.rs | 52 +++++++++++++++++++++++++++--- crates/search/src/search_bar.rs | 2 +- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 0b45455fae..d17efa6350 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -432,10 +432,8 @@ impl Render for BufferSearchBar { })) .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)) - }) + .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)) @@ -2549,6 +2547,52 @@ mod tests { ); } + #[gpui::test] + async fn test_replace_focus(cx: &mut TestAppContext) { + let (editor, search_bar, cx) = init_test(cx); + + editor.update_in(cx, |editor, window, cx| { + editor.set_text("What a bad day!", window, cx) + }); + + search_bar + .update_in(cx, |search_bar, window, cx| { + search_bar.search("bad", None, true, window, cx) + }) + .await + .unwrap(); + + // Calling `toggle_replace` in the search bar ensures that the "Replace + // *" buttons are rendered, so we can then simulate clicking the + // buttons. + search_bar.update_in(cx, |search_bar, window, cx| { + search_bar.toggle_replace(&ToggleReplace, window, cx) + }); + + search_bar.update_in(cx, |search_bar, window, cx| { + search_bar.replacement_editor.update(cx, |editor, cx| { + editor.set_text("great", window, cx); + }); + }); + + // Focus on the editor instead of the search bar, as we want to ensure + // that pressing the "Replace Next Match" button will work, even if the + // search bar is not focused. + cx.focus(&editor); + + // We'll not simulate clicking the "Replace Next Match " button, asserting that + // the replacement was done. + let button_bounds = cx + .debug_bounds("ICON-ReplaceNext") + .expect("'Replace Next Match' button should be visible"); + cx.simulate_click(button_bounds.center(), gpui::Modifiers::none()); + + assert_eq!( + editor.read_with(cx, |editor, cx| editor.text(cx)), + "What a great day!" + ); + } + struct ReplacementTestParams<'a> { editor: &'a Entity, search_bar: &'a Entity, diff --git a/crates/search/src/search_bar.rs b/crates/search/src/search_bar.rs index 61fa46ed97..13b4df9574 100644 --- a/crates/search/src/search_bar.rs +++ b/crates/search/src/search_bar.rs @@ -29,7 +29,7 @@ pub(super) fn render_action_button( if !focus_handle.is_focused(window) { window.focus(&focus_handle); } - window.dispatch_action(action.boxed_clone(), cx) + window.dispatch_action(action.boxed_clone(), cx); } }) .tooltip(move |_window, cx| Tooltip::for_action_in(tooltip, action, &focus_handle, cx))