From d61e80b85debf0c56ecdcadf635ffa2660ddfd37 Mon Sep 17 00:00:00 2001 From: feeiyu <158308373+feeiyu@users.noreply.github.com> Date: Thu, 6 Aug 2026 14:38:10 +0800 Subject: [PATCH] git_panel: Fix focus navigation across Changes and History (#62215) # Objective Fix several Git panel focus issues: - Directional navigation could not move focus into the History tab. - History focus state was not updated when the panel received focus. - Switching back to Changes could leave focus on the panel instead of the commit editor. Fixes #62211 ## Solution - Use the Git panel root as the activation focus target when History is active. - Reuse the panel's focus handle for focus events. - Use the appropriate activation target when switching tabs. ## Testing - Add a regression test `test_history_tab_pane_navigation_focuses_rendered_panel` for navigating between the editor and History. ## 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 adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase after fix: [2026-08-05 22-45-38.webm](https://github.com/user-attachments/assets/fdb3f87a-f1af-4ae1-95f7-0d61acfb3095) --- Release Notes: - Fixed directional focus navigation into the Git panel's History tab. --- crates/git_ui/src/git_panel.rs | 62 +++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index b4f36c7b0b..6735bb57dd 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -1222,7 +1222,7 @@ impl GitPanel { view_mode: GitPanelViewMode::from_settings(cx), tree_expanded_dirs: HashMap::default(), projected_entries_by_path: HashMap::default(), - focus_handle: cx.focus_handle(), + focus_handle, fs, new_count: 0, new_staged_count: 0, @@ -6306,13 +6306,12 @@ impl GitPanel { return; } self.active_tab = tab; + self.activation_focus_handle(cx).focus(window, cx); match tab { GitPanelTab::History => { - self.focus_handle.focus(window, cx); self.load_commit_history(cx); } GitPanelTab::Changes => { - self.focus_handle.focus(window, cx); self.set_commit_history(CommitHistory::Loading, cx); self._repo_subscriptions.clear(); } @@ -8201,7 +8200,9 @@ impl editor::Addon for GitPanelAddon { impl Panel for GitPanel { fn activation_focus_handle(&self, cx: &App) -> FocusHandle { - if self.entries.is_empty() || self.commit_editor_expanded { + if self.active_tab == GitPanelTab::Changes + && (self.entries.is_empty() || self.commit_editor_expanded) + { self.commit_editor.focus_handle(cx) } else { self.focus_handle.clone() @@ -8809,7 +8810,10 @@ mod tests { use util::path; use util::rel_path::rel_path; - use workspace::{MultiWorkspace, ToolbarItemEvent, ToolbarItemLocation}; + use workspace::{ + ActivatePaneLeft, ActivatePaneRight, MultiWorkspace, ToolbarItemEvent, ToolbarItemLocation, + item::test::TestItem, + }; use super::*; @@ -12451,6 +12455,54 @@ mod tests { }); } + #[gpui::test] + async fn test_history_tab_pane_navigation_focuses_rendered_panel(cx: &mut TestAppContext) { + init_test(cx); + + let (_, _project, workspace, panel, mut cx) = + setup_git_panel_with_changes(cx, json!({ ".git": {} }), &[]).await; + + let center_item = workspace.update_in(&mut cx, |workspace, window, cx| { + let center_item = cx.new(TestItem::new); + workspace.add_item_to_active_pane( + Box::new(center_item.clone()), + None, + true, + window, + cx, + ); + workspace.add_panel(panel.clone(), window, cx); + workspace.open_panel::(window, cx); + center_item + }); + panel.update_in(&mut cx, |panel, window, cx| { + assert!(panel.entries.is_empty()); + panel.activate_history_tab(&ActivateHistoryTab, window, cx); + }); + center_item.update_in(&mut cx, |center_item, window, cx| { + center_item.focus_handle(cx).focus(window, cx); + }); + cx.run_until_parked(); + + cx.dispatch_action(ActivatePaneRight); + cx.run_until_parked(); + + let history_panel_was_focused = panel.update_in(&mut cx, |panel, window, cx| { + panel.focus_handle.contains_focused(window, cx) + }); + + cx.dispatch_action(ActivatePaneLeft); + cx.run_until_parked(); + + let center_item_is_focused = center_item.update_in(&mut cx, |center_item, window, cx| { + center_item.focus_handle(cx).is_focused(window) + }); + assert!( + history_panel_was_focused && center_item_is_focused, + "pane navigation should focus the History panel after moving right and restore the center item after moving left; History panel focused after moving right: {history_panel_was_focused}, center item focused after moving left: {center_item_is_focused}" + ); + } + #[gpui::test] async fn test_discard_tracked_changes_respects_staging(cx: &mut TestAppContext) { init_test(cx);