Make repo and branch popovers extend up from their trigger buttons (#26950)

Max Brunsfeld created

Previously, when clicking on the branch, the popover would obscure the
button you just clicked, which was awkward.

Release Notes:

- Improved the placement of the repo and branch picker popovers in the
git panel.
- Added a 'SelectRepo' action that opens the repository selector in a
modal.

Change summary

crates/git_ui/src/branch_picker.rs       | 20 +++++++++++-------
crates/git_ui/src/commit_modal.rs        | 12 ----------
crates/git_ui/src/git_panel.rs           | 27 ++++++++++---------------
crates/git_ui/src/git_ui.rs              | 11 ++++++---
crates/git_ui/src/project_diff.rs        |  7 -----
crates/git_ui/src/repository_selector.rs | 25 ++++++++++++++++++++++-
crates/project/src/git.rs                |  4 ++
crates/zed_actions/src/lib.rs            |  2 
8 files changed, 59 insertions(+), 49 deletions(-)

Detailed changes

crates/git_ui/src/branch_picker.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
     InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, Render,
     SharedString, Styled, Subscription, Task, Window,
 };
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEditorPosition};
 use project::git::Repository;
 use std::sync::Arc;
 use time::OffsetDateTime;
@@ -17,13 +17,10 @@ use util::ResultExt;
 use workspace::notifications::DetachAndPromptErr;
 use workspace::{ModalView, Workspace};
 
-pub fn init(cx: &mut App) {
-    cx.observe_new(|workspace: &mut Workspace, _, _| {
-        workspace.register_action(open);
-        workspace.register_action(switch);
-        workspace.register_action(checkout_branch);
-    })
-    .detach();
+pub fn register(workspace: &mut Workspace) {
+    workspace.register_action(open);
+    workspace.register_action(switch);
+    workspace.register_action(checkout_branch);
 }
 
 pub fn checkout_branch(
@@ -225,6 +222,13 @@ impl PickerDelegate for BranchListDelegate {
         "Select branch...".into()
     }
 
+    fn editor_position(&self) -> PickerEditorPosition {
+        match self.style {
+            BranchListStyle::Modal => PickerEditorPosition::Start,
+            BranchListStyle::Popover => PickerEditorPosition::End,
+        }
+    }
+
     fn match_count(&self) -> usize {
         self.matches.len()
     }

crates/git_ui/src/commit_modal.rs 🔗

@@ -54,16 +54,6 @@ impl ModalContainerProperties {
     }
 }
 
-pub fn init(cx: &mut App) {
-    cx.observe_new(|workspace: &mut Workspace, window, cx| {
-        let Some(window) = window else {
-            return;
-        };
-        CommitModal::register(workspace, window, cx)
-    })
-    .detach();
-}
-
 pub struct CommitModal {
     git_panel: Entity<GitPanel>,
     commit_editor: Entity<Editor>,
@@ -108,7 +98,7 @@ struct RestoreDock {
 }
 
 impl CommitModal {
-    pub fn register(workspace: &mut Workspace, _: &mut Window, _cx: &mut Context<Workspace>) {
+    pub fn register(workspace: &mut Workspace) {
         workspace.register_action(|workspace, _: &Commit, window, cx| {
             CommitModal::toggle(workspace, window, cx);
         });

crates/git_ui/src/git_panel.rs 🔗

@@ -127,18 +127,13 @@ const GIT_PANEL_KEY: &str = "GitPanel";
 
 const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
 
-pub fn init(cx: &mut App) {
-    cx.observe_new(
-        |workspace: &mut Workspace, _window, _: &mut Context<Workspace>| {
-            workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
-                workspace.toggle_panel_focus::<GitPanel>(window, cx);
-            });
-            workspace.register_action(|workspace, _: &ExpandCommitEditor, window, cx| {
-                CommitModal::toggle(workspace, window, cx)
-            });
-        },
-    )
-    .detach();
+pub fn register(workspace: &mut Workspace) {
+    workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
+        workspace.toggle_panel_focus::<GitPanel>(window, cx);
+    });
+    workspace.register_action(|workspace, _: &ExpandCommitEditor, window, cx| {
+        CommitModal::toggle(workspace, window, cx)
+    });
 }
 
 #[derive(Debug, Clone)]
@@ -3829,7 +3824,7 @@ impl Render for GitPanel {
                 deferred(
                     anchored()
                         .position(*position)
-                        .anchor(gpui::Corner::TopLeft)
+                        .anchor(Corner::TopLeft)
                         .child(menu.clone()),
                 )
                 .with_priority(1)
@@ -4087,14 +4082,14 @@ impl RenderOnce for PanelRepoFooter {
                 let project = project.clone();
                 move |window, cx| {
                     let project = project.clone()?;
-                    Some(cx.new(|cx| RepositorySelector::new(project, window, cx)))
+                    Some(cx.new(|cx| RepositorySelector::new(project, rems(16.), window, cx)))
                 }
             })
             .trigger_with_tooltip(
                 repo_selector_trigger.disabled(single_repo).truncate(true),
                 Tooltip::text("Switch active repository"),
             )
-            .attach(gpui::Corner::BottomLeft)
+            .anchor(Corner::BottomLeft)
             .into_any_element();
 
         let branch_selector_button = Button::new("branch-selector", truncated_branch_name)
@@ -4116,7 +4111,7 @@ impl RenderOnce for PanelRepoFooter {
                 branch_selector_button,
                 Tooltip::for_action_title("Switch Branch", &zed_actions::git::Branch),
             )
-            .anchor(Corner::TopLeft)
+            .anchor(Corner::BottomLeft)
             .offset(gpui::Point {
                 x: px(0.0),
                 y: px(-2.0),

crates/git_ui/src/git_ui.rs 🔗

@@ -2,6 +2,7 @@ use std::any::Any;
 
 use ::settings::Settings;
 use command_palette_hooks::CommandPaletteFilter;
+use commit_modal::CommitModal;
 use git::{
     repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus},
     status::{FileStatus, StatusCode, UnmergedStatus, UnmergedStatusCode},
@@ -28,12 +29,14 @@ actions!(git, [ResetOnboarding]);
 
 pub fn init(cx: &mut App) {
     GitPanelSettings::register(cx);
-    branch_picker::init(cx);
-    cx.observe_new(ProjectDiff::register).detach();
-    commit_modal::init(cx);
-    git_panel::init(cx);
 
     cx.observe_new(|workspace: &mut Workspace, _, cx| {
+        ProjectDiff::register(workspace, cx);
+        CommitModal::register(workspace);
+        git_panel::register(workspace);
+        repository_selector::register(workspace);
+        branch_picker::register(workspace);
+
         let project = workspace.project().read(cx);
         if project.is_read_only(cx) {
             return;

crates/git_ui/src/project_diff.rs 🔗

@@ -66,16 +66,11 @@ const TRACKED_NAMESPACE: &'static str = "1";
 const NEW_NAMESPACE: &'static str = "2";
 
 impl ProjectDiff {
-    pub(crate) fn register(
-        workspace: &mut Workspace,
-        _window: Option<&mut Window>,
-        cx: &mut Context<Workspace>,
-    ) {
+    pub(crate) fn register(workspace: &mut Workspace, cx: &mut Context<Workspace>) {
         workspace.register_action(Self::deploy);
         workspace.register_action(|workspace, _: &Add, window, cx| {
             Self::deploy(workspace, &Diff, window, cx);
         });
-
         workspace::register_serializable_item::<ProjectDiff>(cx);
     }
 

crates/git_ui/src/repository_selector.rs 🔗

@@ -9,14 +9,33 @@ use project::{
 };
 use std::sync::Arc;
 use ui::{prelude::*, ListItem, ListItemSpacing};
+use workspace::{ModalView, Workspace};
+
+pub fn register(workspace: &mut Workspace) {
+    workspace.register_action(open);
+}
+
+pub fn open(
+    workspace: &mut Workspace,
+    _: &zed_actions::git::SelectRepo,
+    window: &mut Window,
+    cx: &mut Context<Workspace>,
+) {
+    let project = workspace.project().clone();
+    workspace.toggle_modal(window, cx, |window, cx| {
+        RepositorySelector::new(project, rems(34.), window, cx)
+    })
+}
 
 pub struct RepositorySelector {
+    width: Rems,
     picker: Entity<Picker<RepositorySelectorDelegate>>,
 }
 
 impl RepositorySelector {
     pub fn new(
         project_handle: Entity<Project>,
+        width: Rems,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Self {
@@ -48,7 +67,7 @@ impl RepositorySelector {
                 .max_height(Some(rems(20.).into()))
         });
 
-        RepositorySelector { picker }
+        RepositorySelector { picker, width }
     }
 }
 
@@ -91,10 +110,12 @@ impl Focusable for RepositorySelector {
 
 impl Render for RepositorySelector {
     fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
-        self.picker.clone()
+        div().w(self.width).child(self.picker.clone())
     }
 }
 
+impl ModalView for RepositorySelector {}
+
 pub struct RepositorySelectorDelegate {
     project: WeakEntity<Project>,
     repository_selector: WeakEntity<RepositorySelector>,

crates/project/src/git.rs 🔗

@@ -2039,7 +2039,9 @@ impl Repository {
 
             let mut path = PathBuf::new();
             path = path.join(worktree_name);
-            path = path.join(project_path.path);
+            if project_path.path.components().count() > 0 {
+                path = path.join(project_path.path);
+            }
             Some(path.to_string_lossy().to_string())
         })
         .unwrap_or_else(|| self.repository_entry.work_directory.display_name())

crates/zed_actions/src/lib.rs 🔗

@@ -116,7 +116,7 @@ pub mod workspace {
 pub mod git {
     use gpui::{action_with_deprecated_aliases, actions};
 
-    actions!(git, [CheckoutBranch, Switch]);
+    actions!(git, [CheckoutBranch, Switch, SelectRepo]);
     action_with_deprecated_aliases!(git, Branch, ["branches::OpenRecent"]);
 }