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.
This commit is contained in:
feeiyu authored and GitHub committed 2026-08-06 06:38:10 +00:00
1 parent 82878540b5
commit d61e80b85d
1 file changed
+57 -5
+57 -5
View File
@@ -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::<GitPanel>(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);