From 3bd76bb912cb68a1600a6ee4235576afa40fa496 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Mon, 24 Aug 2026 20:30:19 +0000 Subject: [PATCH] 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. --- crates/git_ui/src/git_panel.rs | 63 +++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 661ba22169..77aabce3bf 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -1764,18 +1764,21 @@ impl GitPanel { _window: &mut Window, cx: &mut Context, ) { + 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) {