Add git awareness to file tab icons (#16637)

Evren Sen and evrensen467 created

Before:
<img width="536" alt="Screenshot 2024-08-22 at 15 58 22"
src="https://github.com/user-attachments/assets/7d957f82-cb09-451e-b944-28d57220a718">

After:
<img width="542" alt="Screenshot 2024-08-22 at 15 58 32"
src="https://github.com/user-attachments/assets/fc203c90-903a-4c35-8af0-45cca66cb9d6">


Release Notes:

- N/A

---------

Co-authored-by: evrensen467 <146845123+evrensen467@users.noreply.github.com>

Change summary

Cargo.lock                   |  1 
crates/workspace/Cargo.toml  |  1 
crates/workspace/src/pane.rs | 49 ++++++++++++++++++++++++++++++++-----
3 files changed, 44 insertions(+), 7 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -13802,6 +13802,7 @@ dependencies = [
  "env_logger",
  "fs",
  "futures 0.3.30",
+ "git",
  "gpui",
  "http_client",
  "itertools 0.11.0",

crates/workspace/Cargo.toml 🔗

@@ -39,6 +39,7 @@ db.workspace = true
 derive_more.workspace = true
 fs.workspace = true
 futures.workspace = true
+git.workspace = true
 gpui.workspace = true
 http_client.workspace = true
 itertools.workspace = true

crates/workspace/src/pane.rs 🔗

@@ -12,6 +12,7 @@ use crate::{
 use anyhow::Result;
 use collections::{BTreeSet, HashMap, HashSet, VecDeque};
 use futures::{stream::FuturesUnordered, StreamExt};
+use git::repository::GitFileStatus;
 use gpui::{
     actions, anchored, deferred, impl_actions, prelude::*, Action, AnchorCorner, AnyElement,
     AppContext, AsyncWindowContext, ClickEvent, ClipboardItem, Div, DragMoveEvent, EntityId,
@@ -1688,6 +1689,31 @@ impl Pane {
         }
     }
 
+    pub fn icon_color(selected: bool) -> Color {
+        if selected {
+            Color::Default
+        } else {
+            Color::Muted
+        }
+    }
+
+    pub fn git_aware_icon_color(
+        git_status: Option<GitFileStatus>,
+        ignored: bool,
+        selected: bool,
+    ) -> Color {
+        if ignored {
+            Color::Ignored
+        } else {
+            match git_status {
+                Some(GitFileStatus::Added) => Color::Created,
+                Some(GitFileStatus::Modified) => Color::Modified,
+                Some(GitFileStatus::Conflict) => Color::Conflict,
+                None => Self::icon_color(selected),
+            }
+        }
+    }
+
     fn render_tab(
         &self,
         ix: usize,
@@ -1695,6 +1721,8 @@ impl Pane {
         detail: usize,
         cx: &mut ViewContext<'_, Pane>,
     ) -> impl IntoElement {
+        let project_path = item.project_path(cx);
+
         let is_active = ix == self.active_item_index;
         let is_preview = self
             .preview_item_id
@@ -1709,6 +1737,19 @@ impl Pane {
             },
             cx,
         );
+
+        let icon_color = if ItemSettings::get_global(cx).git_status {
+            project_path
+                .as_ref()
+                .and_then(|path| self.project.read(cx).entry_for_path(&path, cx))
+                .map(|entry| {
+                    Self::git_aware_icon_color(entry.git_status, entry.is_ignored, is_active)
+                })
+                .unwrap_or_else(|| Self::icon_color(is_active))
+        } else {
+            Self::icon_color(is_active)
+        };
+
         let icon = item.tab_icon(cx);
         let close_side = &ItemSettings::get_global(cx).close_position;
         let indicator = render_item_indicator(item.boxed_clone(), cx);
@@ -1800,13 +1841,7 @@ impl Pane {
             .child(
                 h_flex()
                     .gap_1()
-                    .children(icon.map(|icon| {
-                        icon.size(IconSize::Small).color(if is_active {
-                            Color::Default
-                        } else {
-                            Color::Muted
-                        })
-                    }))
+                    .children(icon.map(|icon| icon.size(IconSize::Small).color(icon_color)))
                     .child(label),
             );