Sync git button states between project diff & git panel (#26938)

Anthony Eid , Conrad Irwin , and Piotr Osiewicz created

Closes #ISSUE

Release Notes:

- Git action buttons are now synced between the project diff and git
panel

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Piotr Osiewicz <peterosiewicz@gmail.com>

Change summary

crates/git_ui/src/git_panel.rs    | 94 +++++++++++++-------------------
crates/git_ui/src/git_ui.rs       |  7 --
crates/git_ui/src/project_diff.rs | 81 ++++++++++++----------------
3 files changed, 74 insertions(+), 108 deletions(-)

Detailed changes

crates/git_ui/src/git_panel.rs 🔗

@@ -2006,7 +2006,7 @@ impl GitPanel {
     }
 
     fn can_push_and_pull(&self, cx: &App) -> bool {
-        crate::can_push_and_pull(&self.project, cx)
+        !self.project.read(cx).is_via_collab()
     }
 
     fn get_current_remote(
@@ -2822,6 +2822,36 @@ impl GitPanel {
         )
     }
 
+    pub(crate) fn render_remote_button(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
+        let branch = self
+            .active_repository
+            .as_ref()?
+            .read(cx)
+            .current_branch()
+            .cloned();
+        if !self.can_push_and_pull(cx) {
+            return None;
+        }
+        let spinner = self.render_spinner();
+        Some(
+            h_flex()
+                .gap_1()
+                .flex_shrink_0()
+                .children(spinner)
+                .when_some(branch, |this, branch| {
+                    let focus_handle = Some(self.focus_handle(cx));
+
+                    this.children(render_remote_button(
+                        "remote-button",
+                        &branch,
+                        focus_handle,
+                        true,
+                    ))
+                })
+                .into_any_element(),
+        )
+    }
+
     pub fn render_footer(
         &self,
         window: &mut Window,
@@ -2861,12 +2891,7 @@ impl GitPanel {
         });
 
         let footer = v_flex()
-            .child(PanelRepoFooter::new(
-                "footer-button",
-                display_name,
-                branch,
-                Some(git_panel),
-            ))
+            .child(PanelRepoFooter::new(display_name, branch, Some(git_panel)))
             .child(
                 panel_editor_container(window, cx)
                     .id("commit-editor-container")
@@ -3959,7 +3984,6 @@ impl Render for GitPanelMessageTooltip {
 #[derive(IntoElement, IntoComponent)]
 #[component(scope = "Version Control")]
 pub struct PanelRepoFooter {
-    id: SharedString,
     active_repository: SharedString,
     branch: Option<Branch>,
     // Getting a GitPanel in previews will be difficult.
@@ -3970,26 +3994,19 @@ pub struct PanelRepoFooter {
 
 impl PanelRepoFooter {
     pub fn new(
-        id: impl Into<SharedString>,
         active_repository: SharedString,
         branch: Option<Branch>,
         git_panel: Option<Entity<GitPanel>>,
     ) -> Self {
         Self {
-            id: id.into(),
             active_repository,
             branch,
             git_panel,
         }
     }
 
-    pub fn new_preview(
-        id: impl Into<SharedString>,
-        active_repository: SharedString,
-        branch: Option<Branch>,
-    ) -> Self {
+    pub fn new_preview(active_repository: SharedString, branch: Option<Branch>) -> Self {
         Self {
-            id: id.into(),
             active_repository,
             branch,
             git_panel: None,
@@ -4105,11 +4122,6 @@ impl RenderOnce for PanelRepoFooter {
                 y: px(-2.0),
             });
 
-        let spinner = self
-            .git_panel
-            .as_ref()
-            .and_then(|git_panel| git_panel.read(cx).render_spinner());
-
         h_flex()
             .w_full()
             .px_2()
@@ -4144,28 +4156,11 @@ impl RenderOnce for PanelRepoFooter {
                     })
                     .child(branch_selector),
             )
-            .child(
-                h_flex()
-                    .gap_1()
-                    .flex_shrink_0()
-                    .children(spinner)
-                    .when_some(branch, |this, branch| {
-                        let mut focus_handle = None;
-                        if let Some(git_panel) = self.git_panel.as_ref() {
-                            if !git_panel.read(cx).can_push_and_pull(cx) {
-                                return this;
-                            }
-                            focus_handle = Some(git_panel.focus_handle(cx));
-                        }
-
-                        this.children(render_remote_button(
-                            self.id.clone(),
-                            &branch,
-                            focus_handle,
-                            true,
-                        ))
-                    }),
-            )
+            .children(if let Some(git_panel) = self.git_panel {
+                git_panel.update(cx, |git_panel, cx| git_panel.render_remote_button(cx))
+            } else {
+                None
+            })
     }
 }
 
@@ -4256,7 +4251,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "no-branch",
                                 active_repository(1).clone(),
                                 None,
                             ))
@@ -4269,7 +4263,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "unknown-upstream",
                                 active_repository(2).clone(),
                                 Some(branch(unknown_upstream)),
                             ))
@@ -4282,7 +4275,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "no-remote-upstream",
                                 active_repository(3).clone(),
                                 Some(branch(no_remote_upstream)),
                             ))
@@ -4295,7 +4287,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "not-ahead-or-behind",
                                 active_repository(4).clone(),
                                 Some(branch(not_ahead_or_behind_upstream)),
                             ))
@@ -4308,7 +4299,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "behind-remote",
                                 active_repository(5).clone(),
                                 Some(branch(behind_upstream)),
                             ))
@@ -4321,7 +4311,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "ahead-of-remote",
                                 active_repository(6).clone(),
                                 Some(branch(ahead_of_upstream)),
                             ))
@@ -4334,7 +4323,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "ahead-and-behind",
                                 active_repository(7).clone(),
                                 Some(branch(ahead_and_behind_upstream)),
                             ))
@@ -4354,7 +4342,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "short-branch",
                                 SharedString::from("zed"),
                                 Some(custom("main", behind_upstream)),
                             ))
@@ -4367,7 +4354,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "long-branch",
                                 SharedString::from("zed"),
                                 Some(custom(
                                     "redesign-and-update-git-ui-list-entry-style",
@@ -4383,7 +4369,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "long-repo",
                                 SharedString::from("zed-industries-community-examples"),
                                 Some(custom("gpui", ahead_of_upstream)),
                             ))
@@ -4396,7 +4381,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "long-repo-and-branch",
                                 SharedString::from("zed-industries-community-examples"),
                                 Some(custom(
                                     "redesign-and-update-git-ui-list-entry-style",
@@ -4412,7 +4396,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "uppercase-repo",
                                 SharedString::from("LICENSES"),
                                 Some(custom("main", ahead_of_upstream)),
                             ))
@@ -4425,7 +4408,6 @@ impl ComponentPreview for PanelRepoFooter {
                             .w(example_width)
                             .overflow_hidden()
                             .child(PanelRepoFooter::new_preview(
-                                "uppercase-branch",
                                 SharedString::from("zed"),
                                 Some(custom("update-README", behind_upstream)),
                             ))

crates/git_ui/src/git_ui.rs 🔗

@@ -7,9 +7,8 @@ use git::{
     status::{FileStatus, StatusCode, UnmergedStatus, UnmergedStatusCode},
 };
 use git_panel_settings::GitPanelSettings;
-use gpui::{actions, App, Entity, FocusHandle};
+use gpui::{actions, App, FocusHandle};
 use onboarding::{clear_dismissed, GitOnboardingModal};
-use project::Project;
 use project_diff::ProjectDiff;
 use ui::prelude::*;
 use workspace::Workspace;
@@ -120,10 +119,6 @@ pub fn git_status_icon(status: FileStatus) -> impl IntoElement {
     GitStatusIcon::new(status)
 }
 
-fn can_push_and_pull(project: &Entity<Project>, cx: &App) -> bool {
-    !project.read(cx).is_via_collab()
-}
-
 fn render_remote_button(
     id: impl Into<SharedString>,
     branch: &Branch,

crates/git_ui/src/project_diff.rs 🔗

@@ -673,8 +673,6 @@ impl Render for ProjectDiff {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let is_empty = self.multibuffer.read(cx).is_empty();
 
-        let can_push_and_pull = crate::can_push_and_pull(&self.project, cx);
-
         div()
             .track_focus(&self.focus_handle)
             .key_context(if is_empty { "EmptyPane" } else { "GitDiff" })
@@ -684,6 +682,16 @@ impl Render for ProjectDiff {
             .justify_center()
             .size_full()
             .when(is_empty, |el| {
+                let remote_button = if let Some(panel) = self
+                    .workspace
+                    .upgrade()
+                    .and_then(|workspace| workspace.read(cx).panel::<GitPanel>(cx))
+                {
+                    panel.update(cx, |panel, cx| panel.render_remote_button(cx))
+                } else {
+                    None
+                };
+                let keybinding_focus_handle = self.focus_handle(cx).clone();
                 el.child(
                     v_flex()
                         .gap_1()
@@ -692,52 +700,33 @@ impl Render for ProjectDiff {
                                 .justify_around()
                                 .child(Label::new("No uncommitted changes")),
                         )
-                        .when(can_push_and_pull, |this_div| {
-                            let keybinding_focus_handle = self.focus_handle(cx);
-
-                            this_div.when_some(self.current_branch.as_ref(), |this_div, branch| {
-                                let remote_button = crate::render_remote_button(
-                                    "project-diff-remote-button",
-                                    branch,
-                                    Some(keybinding_focus_handle.clone()),
-                                    false,
-                                );
-
-                                match remote_button {
-                                    Some(button) => {
-                                        this_div.child(h_flex().justify_around().child(button))
-                                    }
-                                    None => this_div.child(
-                                        h_flex()
-                                            .justify_around()
-                                            .child(Label::new("Remote up to date")),
-                                    ),
-                                }
-                            })
+                        .map(|el| match remote_button {
+                            Some(button) => el.child(h_flex().justify_around().child(button)),
+                            None => el.child(
+                                h_flex()
+                                    .justify_around()
+                                    .child(Label::new("Remote up to date")),
+                            ),
                         })
-                        .map(|this| {
-                            let keybinding_focus_handle = self.focus_handle(cx).clone();
-
-                            this.child(
-                                h_flex().justify_around().mt_1().child(
-                                    Button::new("project-diff-close-button", "Close")
-                                        // .style(ButtonStyle::Transparent)
-                                        .key_binding(KeyBinding::for_action_in(
-                                            &CloseActiveItem::default(),
-                                            &keybinding_focus_handle,
-                                            window,
+                        .child(
+                            h_flex().justify_around().mt_1().child(
+                                Button::new("project-diff-close-button", "Close")
+                                    // .style(ButtonStyle::Transparent)
+                                    .key_binding(KeyBinding::for_action_in(
+                                        &CloseActiveItem::default(),
+                                        &keybinding_focus_handle,
+                                        window,
+                                        cx,
+                                    ))
+                                    .on_click(move |_, window, cx| {
+                                        window.focus(&keybinding_focus_handle);
+                                        window.dispatch_action(
+                                            Box::new(CloseActiveItem::default()),
                                             cx,
-                                        ))
-                                        .on_click(move |_, window, cx| {
-                                            window.focus(&keybinding_focus_handle);
-                                            window.dispatch_action(
-                                                Box::new(CloseActiveItem::default()),
-                                                cx,
-                                            );
-                                        }),
-                                ),
-                            )
-                        }),
+                                        );
+                                    }),
+                            ),
+                        ),
                 )
             })
             .when(!is_empty, |el| el.child(self.editor.clone()))