From bbf3b20fc3042950476a51ecdffa0a26862a72b6 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Fri, 6 Jun 2025 15:35:18 -0400 Subject: [PATCH] Fix panic when dragging pinned item left in pinned region (#32263) This was a regression with my recent fixes to pinned tabs. Dragging a pinned tab left in the pinned region would still update the pinned tab count, which would later cause an out-of-bounds later when it used that value to index into a vec. https://zed-industries.slack.com/archives/C04S6T1T7TQ/p1749220447796559 Release Notes: - Fixed a panic caused by dragging a pinned item to the left in the pinned region --- crates/workspace/src/pane.rs | 92 ++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2fc87be2be..e3104076dc 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2929,6 +2929,7 @@ impl Pane { if is_at_same_position || (moved_right && is_pinned_in_to_pane) || (!moved_right && !is_pinned_in_to_pane) + || (!moved_right && was_pinned_in_from_pane) { return; } @@ -4889,6 +4890,97 @@ mod tests { assert_item_labels(&pane_b, ["B!", "A*"], cx); } + #[gpui::test] + async fn test_drag_pinned_tab_throughout_entire_range_of_pinned_tabs_both_directions( + cx: &mut TestAppContext, + ) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + + let project = Project::test(fs, None, cx).await; + let (workspace, cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let pane_a = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + // Add A, B, C and pin all + let item_a = add_labeled_item(&pane_a, "A", false, cx); + let item_b = add_labeled_item(&pane_a, "B", false, cx); + let item_c = add_labeled_item(&pane_a, "C", false, cx); + assert_item_labels(&pane_a, ["A", "B", "C*"], cx); + + pane_a.update_in(cx, |pane, window, cx| { + let ix = pane.index_for_item_id(item_a.item_id()).unwrap(); + pane.pin_tab_at(ix, window, cx); + + let ix = pane.index_for_item_id(item_b.item_id()).unwrap(); + pane.pin_tab_at(ix, window, cx); + + let ix = pane.index_for_item_id(item_c.item_id()).unwrap(); + pane.pin_tab_at(ix, window, cx); + }); + assert_item_labels(&pane_a, ["A!", "B!", "C*!"], cx); + + // Move A to right of B + pane_a.update_in(cx, |pane, window, cx| { + let dragged_tab = DraggedTab { + pane: pane_a.clone(), + item: item_a.boxed_clone(), + ix: 0, + detail: 0, + is_active: true, + }; + pane.handle_tab_drop(&dragged_tab, 1, window, cx); + }); + + // A should be after B and all are pinned + assert_item_labels(&pane_a, ["B!", "A*!", "C!"], cx); + + // Move A to right of C + pane_a.update_in(cx, |pane, window, cx| { + let dragged_tab = DraggedTab { + pane: pane_a.clone(), + item: item_a.boxed_clone(), + ix: 1, + detail: 0, + is_active: true, + }; + pane.handle_tab_drop(&dragged_tab, 2, window, cx); + }); + + // A should be after C and all are pinned + assert_item_labels(&pane_a, ["B!", "C!", "A*!"], cx); + + // Move A to left of C + pane_a.update_in(cx, |pane, window, cx| { + let dragged_tab = DraggedTab { + pane: pane_a.clone(), + item: item_a.boxed_clone(), + ix: 2, + detail: 0, + is_active: true, + }; + pane.handle_tab_drop(&dragged_tab, 1, window, cx); + }); + + // A should be before C and all are pinned + assert_item_labels(&pane_a, ["B!", "A*!", "C!"], cx); + + // Move A to left of B + pane_a.update_in(cx, |pane, window, cx| { + let dragged_tab = DraggedTab { + pane: pane_a.clone(), + item: item_a.boxed_clone(), + ix: 1, + detail: 0, + is_active: true, + }; + pane.handle_tab_drop(&dragged_tab, 0, window, cx); + }); + + // A should be before B and all are pinned + assert_item_labels(&pane_a, ["A*!", "B!", "C!"], cx); + } + #[gpui::test] async fn test_add_item_with_new_item(cx: &mut TestAppContext) { init_test(cx);