From 4392e3a9fcb35a8d796076ea10bbbe4b9e48319f Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Mon, 2 Mar 2026 21:18:56 -0300 Subject: [PATCH] workspace: Fix scrolling to active tab when pinned tabs are present (#50538) When pinned tabs are present, activating an unpinned tab passed the absolute tab index to the scroll handle, which only contains unpinned tabs. This caused the scroll-into-view to silently fail. Subtract `pinned_tab_count` from the index so it maps to the correct child in the unpinned tabs scroll container. Release Notes: - Fixed tab bar not reliably scrolling to the active tab when pinned tabs are present. --- crates/workspace/src/pane.rs | 68 +++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 66e5eeb4734557c818f42b6537859634435fd295..a39be125a5784b8c9d995bb750b9d7ff57a67191 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1468,7 +1468,8 @@ impl Pane { fn update_active_tab(&mut self, index: usize) { if !self.is_tab_pinned(index) { self.suppress_scroll = false; - self.tab_bar_scroll_handle.scroll_to_item(index); + self.tab_bar_scroll_handle + .scroll_to_item(index - self.pinned_tab_count); } } @@ -7935,6 +7936,71 @@ mod tests { ); } + #[gpui::test] + async fn test_pinned_tabs_scroll_to_item_uses_correct_index(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, window, cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + cx.simulate_resize(size(px(400.), px(300.))); + + for label in ["A", "B", "C"] { + add_labeled_item(&pane, label, false, cx); + } + + pane.update_in(cx, |pane, window, cx| { + pane.pin_tab_at(0, window, cx); + pane.pin_tab_at(1, window, cx); + pane.pin_tab_at(2, window, cx); + }); + + for label in ["D", "E", "F", "G", "H", "I", "J", "K"] { + add_labeled_item(&pane, label, false, cx); + } + + assert_item_labels( + &pane, + ["A!", "B!", "C!", "D", "E", "F", "G", "H", "I", "J", "K*"], + cx, + ); + + cx.run_until_parked(); + + // Verify overflow exists (precondition for scroll test) + let scroll_handle = + pane.update_in(cx, |pane, _window, _cx| pane.tab_bar_scroll_handle.clone()); + assert!( + scroll_handle.max_offset().width > px(0.), + "Test requires tab overflow to verify scrolling. Increase tab count or reduce window width." + ); + + // Activate a different tab first, then activate K + // This ensures we're not just re-activating an already-active tab + pane.update_in(cx, |pane, window, cx| { + pane.activate_item(3, true, true, window, cx); + }); + cx.run_until_parked(); + + pane.update_in(cx, |pane, window, cx| { + pane.activate_item(10, true, true, window, cx); + }); + cx.run_until_parked(); + + let scroll_handle = + pane.update_in(cx, |pane, _window, _cx| pane.tab_bar_scroll_handle.clone()); + let k_tab_bounds = cx.debug_bounds("TAB-10").unwrap(); + let scroll_bounds = scroll_handle.bounds(); + + assert!( + k_tab_bounds.left() >= scroll_bounds.left(), + "Active tab K should be scrolled into view" + ); + } + #[gpui::test] async fn test_close_all_items_including_pinned(cx: &mut TestAppContext) { init_test(cx);