git_ui: Prevent crash when selecting collapsed sections (#63164)

## Summary

- Select the first Git panel entry from the visible row indices instead
of treating an entry index as a visible-row position
- Avoid selecting files hidden by collapsed sections in both tree and
flat views
- Add a regression test for collapsing the only populated section in
tree view with no active selection

Fixes ZED-AZ3
Fixes ZED-B0Z

Release Notes:

- Fixed a crash in the Git panel when collapsed sections were used in
tree view.
This commit is contained in:
Cole Miller authored and GitHub committed 2026-08-24 20:30:19 +00:00
1 parent 192772f95e
commit 3bd76bb912
1 file changed
+54 -9
+54 -9
View File
@@ -1764,18 +1764,21 @@ impl GitPanel {
_window: &mut Window,
cx: &mut Context<Self>,
) {
let is_selectable = |index| {
self.entries
.get(index)
.is_some_and(GitListEntry::is_selectable)
};
let first_entry = match &self.view_mode {
GitPanelViewMode::Flat => self
.entries
.visible_flat_entry_indices()
.into_iter()
.find(|&index| is_selectable(index)),
GitPanelViewMode::Tree(state) => state
.logical_indices
.iter()
.position(|entry| entry.status_entry().is_some()),
GitPanelViewMode::Tree(state) => {
let index = self.entries.iter().position(|entry| {
entry.status_entry().is_some() || entry.directory_entry().is_some()
});
index.map(|index| state.logical_indices[index])
}
.copied()
.find(|&index| is_selectable(index)),
};
if let Some(first_entry) = first_entry {
@@ -12389,6 +12392,48 @@ mod tests {
});
}
#[gpui::test]
async fn test_tree_view_select_first_skips_collapsed_section(cx: &mut TestAppContext) {
init_test(cx);
cx.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.git_panel.get_or_insert_default().tree_view = Some(true);
})
});
});
let (_, _, _, panel, mut cx) = setup_git_panel_with_changes(
cx,
json!({
".git": {},
"modified.rs": "fn main() {}",
}),
&[("modified.rs", StatusCode::Modified)],
)
.await;
panel.update_in(&mut cx, |panel, window, cx| {
panel.selected_entry = None;
panel.toggle_section_collapsed(Section::Tracked, window, cx);
let state = panel
.view_mode
.tree_state()
.expect("tree view state should exist");
assert_eq!(state.logical_indices, [0]);
assert!(panel.selected_entry.is_none());
panel.toggle_section_collapsed(Section::Tracked, window, cx);
let selected_entry = panel
.get_selected_entry()
.and_then(GitListEntry::status_entry)
.expect("the first visible file should be selected");
assert_eq!(selected_entry.repo_path, repo_path("modified.rs"));
});
}
fn assert_entry_paths(entries: &[GitListEntry], expected_paths: &[Option<&str>]) {
assert_eq!(entries.len(), expected_paths.len());
for (entry, expected_path) in entries.iter().zip(expected_paths) {