Highlight file finder entries according to their git status (#31469)

Kirill Bulatov created

Configure this with the
```json5
"file_finder": {
  "git_status": true
}
```
settings value.

Before:
<img width="864" alt="before"
src="https://github.com/user-attachments/assets/5943e30f-1105-445e-9398-ea6dd35877c8"
/>

After:
<img width="864" alt="image"
src="https://github.com/user-attachments/assets/56b2fad6-8cdc-4f28-b238-920745231b1f"
/>

After with search matches:
<img width="577" alt="image"
src="https://github.com/user-attachments/assets/8c414575-7daf-43a8-89c2-98137d52b7a0"
/>

Release Notes:

- Start highlighting file finder entries according to their git status

Change summary

assets/settings/default.json                   |  4 +
crates/file_finder/src/file_finder.rs          | 53 ++++++++++++++++---
crates/file_finder/src/file_finder_settings.rs |  9 +++
3 files changed, 55 insertions(+), 11 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -954,7 +954,9 @@
     //    "skip_focus_for_active_in_search": false
     //
     // Default: true
-    "skip_focus_for_active_in_search": true
+    "skip_focus_for_active_in_search": true,
+    // Whether to show the git status in the file finder.
+    "git_status": true
   },
   // Whether or not to remove any trailing whitespace from lines of a buffer
   // before saving it.

crates/file_finder/src/file_finder.rs 🔗

@@ -11,7 +11,7 @@ use futures::future::join_all;
 pub use open_path_prompt::OpenPathDelegate;
 
 use collections::HashMap;
-use editor::Editor;
+use editor::{Editor, items::entry_git_aware_label_color};
 use file_finder_settings::{FileFinderSettings, FileFinderWidth};
 use file_icons::FileIcons;
 use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
@@ -1418,23 +1418,58 @@ impl PickerDelegate for FileFinderDelegate {
         cx: &mut Context<Picker<Self>>,
     ) -> Option<Self::ListItem> {
         let settings = FileFinderSettings::get_global(cx);
+        let path_match = self.matches.get(ix)?;
+
+        let git_status_color = if settings.git_status {
+            let (entry, project_path) = match path_match {
+                Match::History { path, .. } => {
+                    let project = self.project.read(cx);
+                    let project_path = path.project.clone();
+                    let entry = project.entry_for_path(&project_path, cx)?;
+                    Some((entry, project_path))
+                }
+                Match::Search(mat) => {
+                    let project = self.project.read(cx);
+                    let project_path = ProjectPath {
+                        worktree_id: WorktreeId::from_usize(mat.0.worktree_id),
+                        path: mat.0.path.clone(),
+                    };
+                    let entry = project.entry_for_path(&project_path, cx)?;
+                    Some((entry, project_path))
+                }
+            }?;
+
+            let git_status = self
+                .project
+                .read(cx)
+                .project_path_git_status(&project_path, cx)
+                .map(|status| status.summary())
+                .unwrap_or_default();
+            Some(entry_git_aware_label_color(
+                git_status,
+                entry.is_ignored,
+                selected,
+            ))
+        } else {
+            None
+        };
 
-        let path_match = self
-            .matches
-            .get(ix)
-            .expect("Invalid matches state: no element for index {ix}");
-
-        let history_icon = match &path_match {
+        let history_icon = match path_match {
             Match::History { .. } => Icon::new(IconName::HistoryRerun)
-                .color(Color::Muted)
                 .size(IconSize::Small)
+                .color(Color::Muted)
                 .into_any_element(),
             Match::Search(_) => v_flex()
                 .flex_none()
                 .size(IconSize::Small.rems())
                 .into_any_element(),
         };
+
         let (file_name_label, full_path_label) = self.labels_for_match(path_match, window, cx, ix);
+        let file_name_label = match git_status_color {
+            Some(git_status_color) => file_name_label.color(git_status_color),
+            None => file_name_label,
+        };
 
         let file_icon = maybe!({
             if !settings.file_icons {
@@ -1442,7 +1477,7 @@ impl PickerDelegate for FileFinderDelegate {
             }
             let file_name = path_match.path().file_name()?;
             let icon = FileIcons::get_icon(file_name.as_ref(), cx)?;
-            Some(Icon::from_path(icon).color(Color::Muted))
+            Some(Icon::from_path(icon).color(git_status_color.unwrap_or(Color::Muted)))
         });
 
         Some(

crates/file_finder/src/file_finder_settings.rs 🔗

@@ -8,6 +8,7 @@ pub struct FileFinderSettings {
     pub file_icons: bool,
     pub modal_max_width: Option<FileFinderWidth>,
     pub skip_focus_for_active_in_search: bool,
+    pub git_status: bool,
 }
 
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
@@ -24,6 +25,10 @@ pub struct FileFinderSettingsContent {
     ///
     /// Default: true
     pub skip_focus_for_active_in_search: Option<bool>,
+    /// Determines whether to show the git status in the file finder
+    ///
+    /// Default: true
+    pub git_status: Option<bool>,
 }
 
 impl Settings for FileFinderSettings {
@@ -35,7 +40,9 @@ impl Settings for FileFinderSettings {
         sources.json_merge()
     }
 
-    fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
+    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
+        vscode.bool_setting("git.decorations.enabled", &mut current.git_status);
+    }
 }
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]