From a8bf82c4695537df215c3d8963fb03ac433fcf24 Mon Sep 17 00:00:00 2001 From: xcb3d <122720156+xcb3d@users.noreply.github.com> Date: Wed, 21 Jan 2026 01:02:03 +0700 Subject: [PATCH] debugger: Fix crash when dragging pane items to split view (#46806) ## Summary Fixes crash when dragging debugger panel items (terminal/console/variables/frames) to another pane as split view. **Root causes:** 1. Double borrow panic - `pane_group.split()` was called synchronously inside a `Context` update, causing "cannot update Pane while it is already being updated" 2. `unwrap()` calls that panic when items are in transition during drag operations 3. `debug_assert!` in `pane_at_pixel_position` fails when `members` and `bounding_boxes` are temporarily out of sync after deferred split **Fixes:** - Defer entire split+move operation via `cx.spawn_in()` to avoid double borrow - Replace `unwrap()` with graceful early returns in `activate_item` and `run_in_terminal` - Handle `members.len() != bounding_boxes.len()` gracefully in `pane_at_pixel_position` Closes #46784 Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ... --------- Co-authored-by: Anthony Eid --- crates/debugger_ui/src/session/running.rs | 83 +++++++++++------------ 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index d2f0162d348dcab365f71575aa663a2b8212ee48..94878c3f0c0ffdc910b15297cb6573e5c8cd252e 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -335,56 +335,49 @@ pub(crate) fn new_debugger_pane( let source = tab.pane.clone(); let item_id_to_move = item.item_id(); - let Ok(new_split_pane) = pane - .drag_split_direction() - .map(|split_direction| { - weak_running.update(cx, |running, cx| { - let new_pane = - new_debugger_pane(workspace.clone(), project.clone(), window, cx); - let _previous_subscription = running.pane_close_subscriptions.insert( - new_pane.entity_id(), - cx.subscribe_in(&new_pane, window, RunningState::handle_pane_event), - ); - debug_assert!(_previous_subscription.is_none()); - running - .panes - .split(&this_pane, &new_pane, split_direction, cx)?; - anyhow::Ok(new_pane) - }) - }) - .transpose() - else { - return ControlFlow::Break(()); - }; - - match new_split_pane.transpose() { - // Source pane may be the one currently updated, so defer the move. - Ok(Some(new_pane)) => cx - .spawn_in(window, async move |_, cx| { - cx.update(|window, cx| { - move_item( - &source, - &new_pane, - item_id_to_move, - new_pane.read(cx).active_item_index(), - true, - window, - cx, - ); - }) - .ok(); - }) - .detach(), + let Some(split_direction) = pane.drag_split_direction() else { // If we drop into existing pane or current pane, // regular pane drop handler will take care of it, // using the right tab index for the operation. - Ok(None) => return ControlFlow::Continue(()), - err @ Err(_) => { - err.log_err(); - return ControlFlow::Break(()); - } + return ControlFlow::Continue(()); }; + let workspace = workspace.clone(); + let weak_running = weak_running.clone(); + // Source pane may be the one currently updated, so defer the move. + window.defer(cx, move |window, cx| { + let new_pane = weak_running.update(cx, |running, cx| { + let new_pane = + new_debugger_pane(workspace.clone(), project.clone(), window, cx); + let _previous_subscription = running.pane_close_subscriptions.insert( + new_pane.entity_id(), + cx.subscribe_in(&new_pane, window, RunningState::handle_pane_event), + ); + debug_assert!(_previous_subscription.is_none()); + running + .panes + .split(&this_pane, &new_pane, split_direction, cx)?; + anyhow::Ok(new_pane) + }); + + match new_pane.and_then(|r| r) { + Ok(new_pane) => { + move_item( + &source, + &new_pane, + item_id_to_move, + new_pane.read(cx).active_item_index(), + true, + window, + cx, + ); + } + Err(err) => { + log::error!("{err:?}"); + } + }; + }); + ControlFlow::Break(()) } };