Restore git panel header (#26354)

Mikayla Maki created

Let's play around with it. This should not be added to tomorrow's
preview.

Release Notes:

- Git Beta: Added a panel header with an open diff and stage/unstage all
buttons.

Change summary

crates/git_ui/src/git_panel.rs | 47 ++++++++++++++++++++++++++++++++++-
crates/git_ui/src/git_ui.rs    | 16 ++++++++++++
2 files changed, 61 insertions(+), 2 deletions(-)

Detailed changes

crates/git_ui/src/git_panel.rs 🔗

@@ -1,6 +1,7 @@
 use crate::askpass_modal::AskPassModal;
 use crate::commit_modal::CommitModal;
 use crate::git_panel_settings::StatusStyle;
+use crate::project_diff::Diff;
 use crate::remote_output_toast::{RemoteAction, RemoteOutputToast};
 use crate::repository_selector::filtered_repository_entries;
 use crate::{branch_picker, render_remote_button};
@@ -231,6 +232,7 @@ pub struct GitPanel {
     fs: Arc<dyn Fs>,
     hide_scrollbar_task: Option<Task<()>>,
     new_count: usize,
+    entry_count: usize,
     new_staged_count: usize,
     pending: Vec<PendingOperation>,
     pending_commit: Option<Task<()>>,
@@ -381,6 +383,7 @@ impl GitPanel {
             context_menu: None,
             workspace,
             modal_open: false,
+            entry_count: 0,
         };
         git_panel.schedule_update(false, window, cx);
         git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
@@ -1078,7 +1081,7 @@ impl GitPanel {
         });
     }
 
-    fn stage_all(&mut self, _: &StageAll, _window: &mut Window, cx: &mut Context<Self>) {
+    pub fn stage_all(&mut self, _: &StageAll, _window: &mut Window, cx: &mut Context<Self>) {
         let entries = self
             .entries
             .iter()
@@ -1089,7 +1092,7 @@ impl GitPanel {
         self.change_file_stage(true, entries, cx);
     }
 
-    fn unstage_all(&mut self, _: &UnstageAll, _window: &mut Window, cx: &mut Context<Self>) {
+    pub fn unstage_all(&mut self, _: &UnstageAll, _window: &mut Window, cx: &mut Context<Self>) {
         let entries = self
             .entries
             .iter()
@@ -2137,10 +2140,12 @@ impl GitPanel {
         self.tracked_count = 0;
         self.new_staged_count = 0;
         self.tracked_staged_count = 0;
+        self.entry_count = 0;
         for entry in &self.entries {
             let Some(status_entry) = entry.status_entry() else {
                 continue;
             };
+            self.entry_count += 1;
             if repo.has_conflict(&status_entry.repo_path) {
                 self.conflicted_count += 1;
                 if self.entry_staging(status_entry).has_staged() {
@@ -2402,6 +2407,43 @@ impl GitPanel {
         })
     }
 
+    fn render_panel_header(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        let text;
+        let action;
+        let tooltip;
+        if self.total_staged_count() == self.entry_count {
+            text = "Unstage All";
+            action = git::UnstageAll.boxed_clone();
+            tooltip = "git reset";
+        } else {
+            text = "Stage All";
+            action = git::StageAll.boxed_clone();
+            tooltip = "git add --all ."
+        }
+
+        self.panel_header_container(window, cx)
+            .child(
+                Button::new("diff", "Open diff")
+                    .tooltip(Tooltip::for_action_title("Open diff", &Diff))
+                    .on_click(|_, _, cx| {
+                        cx.defer(|cx| {
+                            cx.dispatch_action(&Diff);
+                        })
+                    }),
+            )
+            .child(div().flex_grow()) // spacer
+            .child(
+                Button::new("stage-unstage-all", text)
+                    .tooltip(Tooltip::for_action_title(tooltip, action.as_ref()))
+                    .on_click(move |_, _, cx| {
+                        let action = action.boxed_clone();
+                        cx.defer(move |cx| {
+                            cx.dispatch_action(action.as_ref());
+                        })
+                    }),
+            )
+    }
+
     pub fn render_footer(
         &self,
         window: &mut Window,
@@ -3166,6 +3208,7 @@ impl Render for GitPanel {
             .child(
                 v_flex()
                     .size_full()
+                    .child(self.render_panel_header(window, cx))
                     .map(|this| {
                         if has_entries {
                             this.child(self.render_entries(has_write_access, window, cx))

crates/git_ui/src/git_ui.rs 🔗

@@ -64,6 +64,22 @@ pub fn init(cx: &mut App) {
                 panel.pull(window, cx);
             });
         });
+        workspace.register_action(|workspace, action: &git::StageAll, window, cx| {
+            let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
+                return;
+            };
+            panel.update(cx, |panel, cx| {
+                panel.stage_all(action, window, cx);
+            });
+        });
+        workspace.register_action(|workspace, action: &git::UnstageAll, window, cx| {
+            let Some(panel) = workspace.panel::<git_panel::GitPanel>(cx) else {
+                return;
+            };
+            panel.update(cx, |panel, cx| {
+                panel.unstage_all(action, window, cx);
+            });
+        });
     })
     .detach();
 }