git_ui: Fix select first entry selects the wrong visual first entry when tree view is enabled (#45108)

Remco Smits created

This PR fixes a bug where the select first didn't select the first
visual entry when the first entry is a collapsed directory.

Follow-up: https://github.com/zed-industries/zed/pull/45030

**Before**:


https://github.com/user-attachments/assets/5e5865cc-ec0f-471d-a81b-9521fb70df41

**After**:


https://github.com/user-attachments/assets/05562572-e43f-4d1e-9638-80e4dccc0998

Release Notes:

- git_ui: Fix select first entry selects the wrong visual first entry
when tree view is enabled

Change summary

crates/git_ui/src/git_panel.rs | 42 +++++++++++++++++++++++------------
1 file changed, 27 insertions(+), 15 deletions(-)

Detailed changes

crates/git_ui/src/git_panel.rs 🔗

@@ -279,6 +279,13 @@ impl GitListEntry {
             _ => None,
         }
     }
+
+    fn directory_entry(&self) -> Option<&GitTreeDirEntry> {
+        match self {
+            GitListEntry::Directory(entry) => Some(entry),
+            _ => None,
+        }
+    }
 }
 
 enum GitPanelViewMode {
@@ -895,12 +902,6 @@ impl GitPanel {
         cx.notify();
     }
 
-    fn first_status_entry_index(&self) -> Option<usize> {
-        self.entries
-            .iter()
-            .position(|entry| entry.status_entry().is_some())
-    }
-
     fn expand_selected_entry(
         &mut self,
         _: &ExpandSelectedEntry,
@@ -944,7 +945,21 @@ impl GitPanel {
     }
 
     fn select_first(&mut self, _: &SelectFirst, _window: &mut Window, cx: &mut Context<Self>) {
-        if let Some(first_entry) = self.first_status_entry_index() {
+        let first_entry = match &self.view_mode {
+            GitPanelViewMode::Flat => self
+                .entries
+                .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])
+            }
+        };
+
+        if let Some(first_entry) = first_entry {
             self.selected_entry = Some(first_entry);
             self.scroll_to_selected_entry(cx);
         }
@@ -1053,15 +1068,13 @@ impl GitPanel {
         cx.notify();
     }
 
-    fn select_first_entry_if_none(&mut self, cx: &mut Context<Self>) {
+    fn select_first_entry_if_none(&mut self, window: &mut Window, cx: &mut Context<Self>) {
         let have_entries = self
             .active_repository
             .as_ref()
             .is_some_and(|active_repository| active_repository.read(cx).status_summary().count > 0);
         if have_entries && self.selected_entry.is_none() {
-            self.selected_entry = self.first_status_entry_index();
-            self.scroll_to_selected_entry(cx);
-            cx.notify();
+            self.select_first(&SelectFirst, window, cx);
         }
     }
 
@@ -1071,10 +1084,9 @@ impl GitPanel {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        self.select_first_entry_if_none(cx);
-
         self.focus_handle.focus(window);
-        cx.notify();
+
+        self.select_first_entry_if_none(window, cx);
     }
 
     fn get_selected_entry(&self) -> Option<&GitListEntry> {
@@ -3549,7 +3561,7 @@ impl GitPanel {
             self.bulk_staging = bulk_staging;
         }
 
-        self.select_first_entry_if_none(cx);
+        self.select_first_entry_if_none(window, cx);
 
         let suggested_commit_message = self.suggest_commit_message(cx);
         let placeholder_text = suggested_commit_message.unwrap_or("Enter commit message".into());