Fix a bug where Zed incorrectly keeps a removed pane as the active pane (#56229)

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.
This commit is contained in:
Joseph T. Lyons authored and GitHub committed 2026-05-09 19:33:05 +00:00
1 parent ef65b5a283
commit c8f09caee4
1 file changed
+48 -4
+48 -4
View File
@@ -6768,14 +6768,17 @@ impl Workspace {
window: &mut Window,
cx: &mut Context<Workspace>,
) {
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);