From c5e0fb8ffdb98187be08eae9956852df9ffe4da3 Mon Sep 17 00:00:00 2001 From: Jace Date: Mon, 20 Jul 2026 23:14:52 -0700 Subject: [PATCH] search: Fix toggle soft wrap not working while the search bar is focused (#61150) Closes #34698 The buffer search query input is itself an editor, so dispatching `editor::ToggleSoftWrap` from the command palette while the search bar had focus toggled soft wrap on the single-line query editor (with no visible effect) and never reached the searched editor. This intercepts the action on the search bar in the capture phase and applies it to the active searchable item, the same way `ToggleFoldAll` is already relayed. Focus stays in the search bar, so you can keep typing your query. `Editor::toggle_soft_wrap` and `Editor::soft_wrap_mode` become `pub` (matching `fold_all`/`has_any_buffer_folded`, which the existing `ToggleFoldAll` relay already uses). Includes a regression test that fails without the fix: it deploys the search bar, dispatches `ToggleSoftWrap` with the query editor focused, and asserts the searched editor's wrap mode changes while focus remains in the search bar. Release Notes: - Fixed `editor: toggle soft wrap` doing nothing when invoked while the buffer search bar was focused --------- Co-authored-by: Christopher Biscardi --- crates/editor/src/config.rs | 9 +--- crates/editor/src/split.rs | 2 +- crates/search/src/buffer_search.rs | 86 +++++++++++++++++++++++++++++- 3 files changed, 87 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/config.rs b/crates/editor/src/config.rs index f1a717db7c..6676a790a0 100644 --- a/crates/editor/src/config.rs +++ b/crates/editor/src/config.rs @@ -257,7 +257,7 @@ impl Editor { wrap_guides } - pub(super) fn soft_wrap_mode(&self, cx: &App) -> SoftWrap { + pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap { let settings = self.buffer.read(cx).language_settings(cx); let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap); match mode { @@ -286,12 +286,7 @@ impl Editor { } } - pub(super) fn toggle_soft_wrap( - &mut self, - _: &ToggleSoftWrap, - _: &mut Window, - cx: &mut Context, - ) { + pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context) { if self.soft_wrap_mode_override.is_some() { self.soft_wrap_mode_override.take(); } else { diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index 93a6f96a44..96ed0a23b0 100644 --- a/crates/editor/src/split.rs +++ b/crates/editor/src/split.rs @@ -1158,7 +1158,7 @@ impl SplittableEditor { } } - fn toggle_soft_wrap( + pub fn toggle_soft_wrap( &mut self, _: &ToggleSoftWrap, window: &mut Window, diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index f0fa4acafd..b4818a1160 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -15,7 +15,7 @@ use any_vec::AnyVec; use collections::HashMap; use editor::{ DiffStyleControls, Editor, EditorSettings, MultiBufferOffset, SplittableEditor, - actions::{Backtab, FoldAll, Tab, ToggleFoldAll, UnfoldAll}, + actions::{Backtab, FoldAll, Tab, ToggleFoldAll, ToggleSoftWrap, UnfoldAll}, scroll::Autoscroll, }; use futures::channel::oneshot; @@ -462,6 +462,7 @@ impl Render for BufferSearchBar { .capture_action(cx.listener(Self::tab)) .capture_action(cx.listener(Self::backtab)) .capture_action(cx.listener(Self::toggle_fold_all)) + .capture_action(cx.listener(Self::toggle_soft_wrap)) .on_action(cx.listener(Self::previous_history_query)) .on_action(cx.listener(Self::next_history_query)) .on_action(cx.listener(Self::dismiss)) @@ -995,6 +996,38 @@ impl BufferSearchBar { self.toggle_fold_all_in_item(window, cx); } + // The query editor is an editor itself and would otherwise swallow this + // action without any visible effect, so relay it to the searched editor + // while keeping the focus in the search bar. + fn toggle_soft_wrap( + &mut self, + action: &ToggleSoftWrap, + window: &mut Window, + cx: &mut Context, + ) { + let Some(item) = &self.active_searchable_item else { + return; + }; + // A split diff editor picks which side to toggle and keeps both sides + // in sync, while its `act_as_type(Editor)` only exposes the RHS + // editor, so relay to the split editor itself when it is split. + if let Some(split_editor) = item.act_as_type(TypeId::of::(), cx) { + let split_editor = split_editor + .downcast::() + .expect("Is a splittable editor"); + if split_editor.read(cx).is_split() { + split_editor.update(cx, |split_editor, cx| { + split_editor.toggle_soft_wrap(action, window, cx) + }); + return; + } + } + if let Some(item) = item.act_as_type(TypeId::of::(), cx) { + let editor = item.downcast::().expect("Is an editor"); + editor.update(cx, |editor, cx| editor.toggle_soft_wrap(action, window, cx)); + } + } + fn toggle_fold_all_in_item(&self, window: &mut Window, cx: &mut Context) { if let Some(item) = &self.active_searchable_item { if let Some(item) = item.act_as_type(TypeId::of::(), cx) { @@ -1846,7 +1879,7 @@ mod tests { use super::*; use editor::{ DisplayPoint, Editor, HighlightKey, MultiBuffer, PathKey, - SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT, SearchSettings, SelectionEffects, + SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT, SearchSettings, SelectionEffects, SoftWrap, display_map::DisplayRow, test::editor_test_context::EditorTestContext, }; use futures::stream::StreamExt as _; @@ -3188,6 +3221,55 @@ mod tests { }); } + #[gpui::test] + async fn test_toggle_soft_wrap_relays_to_searched_editor(cx: &mut TestAppContext) { + init_globals(cx); + let (editor, search_bar, cx) = init_test(cx); + + search_bar.update_in(cx, |search_bar, window, cx| { + search_bar.deploy( + &Deploy { + focus: true, + replace_enabled: false, + selection_search_enabled: false, + }, + None, + window, + cx, + ); + }); + cx.run_until_parked(); + + search_bar.update_in(cx, |search_bar, window, cx| { + assert!( + search_bar.query_editor.focus_handle(cx).is_focused(window), + "query editor should be focused after deploying the search bar", + ); + }); + editor.update_in(cx, |editor, _, cx| { + assert!( + matches!(editor.soft_wrap_mode(cx), SoftWrap::None), + "soft wrap should be disabled initially", + ); + }); + + cx.dispatch_action(ToggleSoftWrap); + cx.run_until_parked(); + + editor.update_in(cx, |editor, _, cx| { + assert!( + matches!(editor.soft_wrap_mode(cx), SoftWrap::EditorWidth), + "toggling soft wrap while the search bar is focused should affect the searched editor", + ); + }); + search_bar.update_in(cx, |search_bar, window, cx| { + assert!( + search_bar.query_editor.focus_handle(cx).is_focused(window), + "focus should stay in the search bar", + ); + }); + } + #[cfg(target_os = "macos")] #[gpui::test] async fn test_cmd_e_then_cmd_g_uses_selection_for_find(cx: &mut TestAppContext) {