title_bar: Show git status indicator icon in the title bar (#38029)

Lev Zakharov created

See related discussion #37046.

<details>

<summary>Screenshots</summary>

**No Changes**
<img
src="https://github.com/user-attachments/assets/e814da6e-bc9b-4edd-b37a-6bb4680d5bb3"
/>

**Added**
<img
src="https://github.com/user-attachments/assets/07ffdf90-08cb-43f4-b2bd-9966a21e08de"
/>

**Changed**
<img
src="https://github.com/user-attachments/assets/7e13b999-83b3-41ea-b2ab-baaa1541b169"
/>

**Deleted**
<img
src="https://github.com/user-attachments/assets/a77fc7e3-a026-419a-87bd-7146c3ca46a9"
/>

**Conflicts**
<img
src="https://github.com/user-attachments/assets/17e7e35c-d81b-4660-808d-08e12107ea2d"
/>

</details>

Release Notes:

- Show git status indicator icon in the title bar

Change summary

crates/title_bar/src/title_bar.rs | 80 ++++++++++++++++++++++----------
1 file changed, 54 insertions(+), 26 deletions(-)

Detailed changes

crates/title_bar/src/title_bar.rs 🔗

@@ -30,7 +30,10 @@ use gpui::{
     Subscription, WeakEntity, Window, actions, div,
 };
 use onboarding_banner::OnboardingBanner;
-use project::{Project, WorktreeSettings};
+use project::{
+    Project, WorktreeSettings,
+    git_store::{GitStoreEvent, RepositoryEvent},
+};
 use remote::RemoteConnectionOptions;
 use settings::{Settings, SettingsLocation};
 use std::sync::Arc;
@@ -245,6 +248,7 @@ impl TitleBar {
         cx: &mut Context<Self>,
     ) -> Self {
         let project = workspace.project().clone();
+        let git_store = project.read(cx).git_store().clone();
         let user_store = workspace.app_state().user_store.clone();
         let client = workspace.app_state().client.clone();
         let active_call = ActiveCall::global(cx);
@@ -272,6 +276,17 @@ impl TitleBar {
         subscriptions.push(cx.subscribe(&project, |_, _, _: &project::Event, cx| cx.notify()));
         subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
         subscriptions.push(cx.observe_window_activation(window, Self::window_activation_changed));
+        subscriptions.push(
+            cx.subscribe(&git_store, move |_, _, event, cx| match event {
+                GitStoreEvent::ActiveRepositoryChanged(_)
+                | GitStoreEvent::RepositoryUpdated(_, RepositoryEvent::Updated { .. }, _)
+                | GitStoreEvent::RepositoryAdded(_)
+                | GitStoreEvent::RepositoryRemoved(_) => {
+                    cx.notify();
+                }
+                _ => {}
+            }),
+        );
         subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
 
         let banner = cx.new(|cx| {
@@ -480,24 +495,24 @@ impl TitleBar {
     }
 
     pub fn render_project_branch(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
+        let settings = TitleBarSettings::get_global(cx);
         let repository = self.project.read(cx).active_repository(cx)?;
         let workspace = self.workspace.upgrade()?;
-        let branch_name = {
-            let repo = repository.read(cx);
-            repo.branch
-                .as_ref()
-                .map(|branch| branch.name())
-                .map(|name| util::truncate_and_trailoff(name, MAX_BRANCH_NAME_LENGTH))
-                .or_else(|| {
-                    repo.head_commit.as_ref().map(|commit| {
-                        commit
-                            .sha
-                            .chars()
-                            .take(MAX_SHORT_SHA_LENGTH)
-                            .collect::<String>()
-                    })
+        let repo = repository.read(cx);
+        let branch_name = repo
+            .branch
+            .as_ref()
+            .map(|branch| branch.name())
+            .map(|name| util::truncate_and_trailoff(name, MAX_BRANCH_NAME_LENGTH))
+            .or_else(|| {
+                repo.head_commit.as_ref().map(|commit| {
+                    commit
+                        .sha
+                        .chars()
+                        .take(MAX_SHORT_SHA_LENGTH)
+                        .collect::<String>()
                 })
-        }?;
+            })?;
 
         Some(
             Button::new("project_branch_trigger", branch_name)
@@ -519,16 +534,29 @@ impl TitleBar {
                         window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx);
                     });
                 })
-                .when(
-                    TitleBarSettings::get_global(cx).show_branch_icon,
-                    |branch_button| {
-                        branch_button
-                            .icon(IconName::GitBranch)
-                            .icon_position(IconPosition::Start)
-                            .icon_color(Color::Muted)
-                            .icon_size(IconSize::Indicator)
-                    },
-                ),
+                .when(settings.show_branch_icon, |branch_button| {
+                    let (icon, icon_color) = {
+                        let status = repo.status_summary();
+                        let tracked = status.index + status.worktree;
+                        if status.conflict > 0 {
+                            (IconName::Warning, Color::VersionControlConflict)
+                        } else if tracked.modified > 0 {
+                            (IconName::SquareDot, Color::VersionControlModified)
+                        } else if tracked.added > 0 || status.untracked > 0 {
+                            (IconName::SquarePlus, Color::VersionControlAdded)
+                        } else if tracked.deleted > 0 {
+                            (IconName::SquareMinus, Color::VersionControlDeleted)
+                        } else {
+                            (IconName::GitBranch, Color::Muted)
+                        }
+                    };
+
+                    branch_button
+                        .icon(icon)
+                        .icon_position(IconPosition::Start)
+                        .icon_color(icon_color)
+                        .icon_size(IconSize::Indicator)
+                }),
         )
     }