search: Dismiss modal view when running search action (#39446)

Currently, using cmd-f or cmd-shift-f to search while a modal is active
(e.g. after cmd-t or cmd-p) doesn't do anything — you need to first
close the modal manually before initiating a search. This PR allows
these actions to run regardless of whether a modal is active.

Some context: VSCode lets you do this too, and for me it's quite common
to do a symbol search with cmd-t immediately followed by a regular
search with cmd-shift-f if I don't find what I'm looking for, so having
to close the modal first is slightly disruptive. cmd-t followed by cmd-p
does dismiss the project symbols modal in order to display the file
search modal, so it makes sense to me to also allow search actions to
dismiss an active modal.

Maybe this blunt fix has unintended consequences? If some types of
modals shouldn't be dismissed when running cmd-f, or some actions
shouldn't dismiss a currently active modal, then we'll have to go about
it differently.

Release Notes:

- Added the ability to run search actions when a modal is currently
active
This commit is contained in:
Tim Vermeulen
2025-10-15 19:50:49 +03:00
committed by GitHub
parent ce2259ce51
commit 35595fe3c2
3 changed files with 71 additions and 5 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ impl<T: 'static> SearchActionsRegistrar for DivRegistrar<'_, '_, T> {
impl SearchActionsRegistrar for Workspace {
fn register_handler<A: Action>(&mut self, callback: impl ActionExecutor<A>) {
self.register_action(move |workspace, action: &A, window, cx| {
if workspace.has_active_modal(window, cx) {
if workspace.has_active_modal(window, cx) && !workspace.hide_modal(window, cx) {
cx.propagate();
return;
}
+65 -4
View File
@@ -153,7 +153,7 @@ pub fn init(cx: &mut App) {
// Both on present and dismissed search, we need to unconditionally handle those actions to focus from the editor.
workspace.register_action(move |workspace, action: &DeploySearch, window, cx| {
if workspace.has_active_modal(window, cx) {
if workspace.has_active_modal(window, cx) && !workspace.hide_modal(window, cx) {
cx.propagate();
return;
}
@@ -161,7 +161,7 @@ pub fn init(cx: &mut App) {
cx.notify();
});
workspace.register_action(move |workspace, action: &NewSearch, window, cx| {
if workspace.has_active_modal(window, cx) {
if workspace.has_active_modal(window, cx) && !workspace.hide_modal(window, cx) {
cx.propagate();
return;
}
@@ -2281,7 +2281,7 @@ fn register_workspace_action<A: Action>(
callback: fn(&mut ProjectSearchBar, &A, &mut Window, &mut Context<ProjectSearchBar>),
) {
workspace.register_action(move |workspace, action: &A, window, cx| {
if workspace.has_active_modal(window, cx) {
if workspace.has_active_modal(window, cx) && !workspace.hide_modal(window, cx) {
cx.propagate();
return;
}
@@ -2308,7 +2308,7 @@ fn register_workspace_action_for_present_search<A: Action>(
callback: fn(&mut Workspace, &A, &mut Window, &mut Context<Workspace>),
) {
workspace.register_action(move |workspace, action: &A, window, cx| {
if workspace.has_active_modal(window, cx) {
if workspace.has_active_modal(window, cx) && !workspace.hide_modal(window, cx) {
cx.propagate();
return;
}
@@ -4148,6 +4148,67 @@ pub mod tests {
});
}
#[gpui::test]
async fn test_search_dismisses_modal(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
path!("/dir"),
json!({
"one.rs": "const ONE: usize = 1;",
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
struct EmptyModalView {
focus_handle: gpui::FocusHandle,
}
impl EventEmitter<gpui::DismissEvent> for EmptyModalView {}
impl Render for EmptyModalView {
fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
div()
}
}
impl Focusable for EmptyModalView {
fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl workspace::ModalView for EmptyModalView {}
window
.update(cx, |workspace, window, cx| {
workspace.toggle_modal(window, cx, |_, cx| EmptyModalView {
focus_handle: cx.focus_handle(),
});
assert!(workspace.has_active_modal(window, cx));
})
.unwrap();
cx.dispatch_action(window.into(), Deploy::find());
window
.update(cx, |workspace, window, cx| {
assert!(!workspace.has_active_modal(window, cx));
workspace.toggle_modal(window, cx, |_, cx| EmptyModalView {
focus_handle: cx.focus_handle(),
});
assert!(workspace.has_active_modal(window, cx));
})
.unwrap();
cx.dispatch_action(window.into(), DeploySearch::find());
window
.update(cx, |workspace, window, cx| {
assert!(!workspace.has_active_modal(window, cx));
})
.unwrap();
}
fn init_test(cx: &mut TestAppContext) {
cx.update(|cx| {
let settings = SettingsStore::test(cx);
+5
View File
@@ -5864,6 +5864,11 @@ impl Workspace {
})
}
pub fn hide_modal(&mut self, window: &mut Window, cx: &mut App) -> bool {
self.modal_layer
.update(cx, |modal_layer, cx| modal_layer.hide_modal(window, cx))
}
pub fn toggle_status_toast<V: ToastView>(&mut self, entity: Entity<V>, cx: &mut App) {
self.toast_layer
.update(cx, |toast_layer, cx| toast_layer.toggle_toast(cx, entity))