From c8f09caee42ea4e27991ca55257f6f9c7d7c67d2 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Sat, 9 May 2026 15:33:05 -0400 Subject: [PATCH] Fix a bug where Zed incorrectly keeps a removed pane as the active pane (#56229) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR should fix the issue Ben ran into with Auto Watch. It isn’t a bug in Auto Watch itself, but a bug in Zed’s active-pane bookkeeping when Zed auto-closes a pane. Ben had a screen share in its own split; when the share ended, the tab closed, and with it, the pane. In this case, Zed was holding onto the removed pane as the active pane, so Auto Watch was trying to open the next screen share in a pane that was no longer part of the workspace. Self-Review Checklist: - [X] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [X] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [X] Tests cover the new/changed behavior - [X] Performance impact has been considered and is acceptable Release Notes: - Fixed a bug where Zed incorrectly kept a removed pane as the active pane. --- crates/workspace/src/workspace.rs | 52 ++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6a5b2c0fbf6..1e27eb3f138 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -6768,14 +6768,17 @@ impl Workspace { window: &mut Window, cx: &mut Context, ) { + let removing_active_pane = self.active_pane() == pane; self.panes.retain(|p| p != pane); if let Some(focus_on) = focus_on { + if removing_active_pane { + self.set_active_pane(focus_on, window, cx); + } focus_on.update(cx, |pane, cx| window.focus(&pane.focus_handle(cx), cx)); - } else if self.active_pane() == pane { + } else if removing_active_pane { let fallback_pane = self.panes.last().unwrap().clone(); - if self.has_active_modal(window, cx) { - self.set_active_pane(&fallback_pane, window, cx); - } else { + self.set_active_pane(&fallback_pane, window, cx); + if !self.has_active_modal(window, cx) { fallback_pane.update(cx, |pane, cx| window.focus(&pane.focus_handle(cx), cx)); } } @@ -14829,6 +14832,47 @@ mod tests { }); } + #[gpui::test] + async fn test_active_pane_updates_to_focus_target_on_removal(cx: &mut TestAppContext) { + assert_active_pane_is_replaced_after_removal(cx, true).await; + } + + #[gpui::test] + async fn test_active_pane_updates_to_fallback_on_removal(cx: &mut TestAppContext) { + assert_active_pane_is_replaced_after_removal(cx, false).await; + } + + async fn assert_active_pane_is_replaced_after_removal( + cx: &mut TestAppContext, + use_focus_target: bool, + ) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + let project = Project::test(fs, [], cx).await; + let (workspace, cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + + workspace.update_in(cx, |workspace, window, cx| { + let first_pane = workspace.active_pane().clone(); + let second_pane = + workspace.split_pane(first_pane.clone(), SplitDirection::Right, window, cx); + workspace.set_active_pane(&second_pane, window, cx); + + let focus_target = use_focus_target.then(|| first_pane.clone()); + workspace.remove_pane(second_pane, focus_target, window, cx); + + assert_eq!(workspace.active_pane(), &first_pane); + assert!( + workspace + .panes() + .iter() + .any(|pane| pane == workspace.active_pane()), + "active pane should be one of the remaining workspace panes" + ); + }); + } + #[gpui::test] async fn test_moving_items_create_panes(cx: &mut TestAppContext) { init_test(cx);