Fix terminal pane tabs arrangement and closing (cherry-pick #22013) (#22017)

gcp-cherry-pick-bot[bot] and Kirill Bulatov created

Cherry-picked Fix terminal pane tabs arrangement and closing (#22013)

* Fixes the inability to drag and drop terminal tabs to reorder them;
fixed incorrect terminal tab move on drag and drop into existing pane
(follow-up of https://github.com/zed-industries/zed/pull/21238)
* Fixes save dialogue appearing when on closing terminal tabs with
running tasks (follow-up of
https://github.com/zed-industries/zed/pull/21374)

Release Notes:

- Fixed terminal pane tabs arrangement and closing

Co-authored-by: Kirill Bulatov <kirill@zed.dev>

Change summary

crates/terminal_view/src/terminal_panel.rs | 99 ++++++++++++-----------
crates/workspace/src/pane.rs               |  7 -
2 files changed, 53 insertions(+), 53 deletions(-)

Detailed changes

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -899,8 +899,7 @@ pub fn new_terminal_pane(
         pane.set_custom_drop_handle(cx, move |pane, dropped_item, cx| {
             if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>() {
                 let this_pane = cx.view().clone();
-                let belongs_to_this_pane = tab.pane == this_pane;
-                let item = if belongs_to_this_pane {
+                let item = if tab.pane == this_pane {
                     pane.item_for_index(tab.ix)
                 } else {
                     tab.pane.read(cx).item_for_index(tab.ix)
@@ -910,53 +909,57 @@ pub fn new_terminal_pane(
                         let source = tab.pane.clone();
                         let item_id_to_move = item.item_id();
 
-                        let new_pane = pane.drag_split_direction().and_then(|split_direction| {
-                            terminal_panel.update(cx, |terminal_panel, cx| {
-                                let is_zoomed = if terminal_panel.active_pane == this_pane {
-                                    pane.is_zoomed()
-                                } else {
-                                    terminal_panel.active_pane.read(cx).is_zoomed()
-                                };
-                                let new_pane = new_terminal_pane(
-                                    workspace.clone(),
-                                    project.clone(),
-                                    is_zoomed,
-                                    cx,
-                                );
-                                terminal_panel.apply_tab_bar_buttons(&new_pane, cx);
-                                terminal_panel
-                                    .center
-                                    .split(&this_pane, &new_pane, split_direction)
-                                    .log_err()?;
-                                Some(new_pane)
-                            })
-                        });
-
-                        let destination;
-                        let destination_index;
-                        if let Some(new_pane) = new_pane {
-                            destination_index = new_pane.read(cx).active_item_index();
-                            destination = new_pane;
-                        } else if belongs_to_this_pane {
-                            return ControlFlow::Break(());
-                        } else {
-                            destination = cx.view().clone();
-                            destination_index = pane.active_item_index();
-                        }
-                        // Destination pane may be the one currently updated, so defer the move.
-                        cx.spawn(|_, mut cx| async move {
-                            cx.update(|cx| {
-                                move_item(
-                                    &source,
-                                    &destination,
-                                    item_id_to_move,
-                                    destination_index,
-                                    cx,
-                                );
+                        let new_split_pane = pane
+                            .drag_split_direction()
+                            .map(|split_direction| {
+                                terminal_panel.update(cx, |terminal_panel, cx| {
+                                    let is_zoomed = if terminal_panel.active_pane == this_pane {
+                                        pane.is_zoomed()
+                                    } else {
+                                        terminal_panel.active_pane.read(cx).is_zoomed()
+                                    };
+                                    let new_pane = new_terminal_pane(
+                                        workspace.clone(),
+                                        project.clone(),
+                                        is_zoomed,
+                                        cx,
+                                    );
+                                    terminal_panel.apply_tab_bar_buttons(&new_pane, cx);
+                                    terminal_panel.center.split(
+                                        &this_pane,
+                                        &new_pane,
+                                        split_direction,
+                                    )?;
+                                    anyhow::Ok(new_pane)
+                                })
                             })
-                            .ok();
-                        })
-                        .detach();
+                            .transpose();
+
+                        match new_split_pane {
+                            // Source pane may be the one currently updated, so defer the move.
+                            Ok(Some(new_pane)) => cx
+                                .spawn(|_, mut cx| async move {
+                                    cx.update(|cx| {
+                                        move_item(
+                                            &source,
+                                            &new_pane,
+                                            item_id_to_move,
+                                            new_pane.read(cx).active_item_index(),
+                                            cx,
+                                        );
+                                    })
+                                    .ok();
+                                })
+                                .detach(),
+                            // 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(());
+                            }
+                        };
                     } else if let Some(project_path) = item.project_path(cx) {
                         if let Some(entry_path) = project.read(cx).absolute_path(&project_path, cx)
                         {

crates/workspace/src/pane.rs 🔗

@@ -1429,7 +1429,7 @@ impl Pane {
                     // Always propose to save singleton files without any project paths: those cannot be saved via multibuffer, as require a file path selection modal.
                     || cx
                         .update(|cx| {
-                            item_to_close.is_dirty(cx)
+                            item_to_close.can_save(cx) && item_to_close.is_dirty(cx)
                                 && item_to_close.is_singleton(cx)
                                 && item_to_close.project_path(cx).is_none()
                         })
@@ -3936,11 +3936,8 @@ mod tests {
 
         cx.executor().run_until_parked();
         cx.simulate_prompt_answer(2);
-        cx.executor().run_until_parked();
-        cx.simulate_prompt_answer(2);
-        cx.executor().run_until_parked();
         save.await.unwrap();
-        assert_item_labels(&pane, ["A*^", "B^", "C^"], cx);
+        assert_item_labels(&pane, [], cx);
     }
 
     #[gpui::test]