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

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.
This commit is contained in:
Max Brunsfeld 2025-03-17 15:05:17 -07:00 committed by GitHub
parent a05066cd83
commit 2b2b9c1624
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 59 additions and 49 deletions

View file

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

View file

@ -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 { pub struct CommitModal {
git_panel: Entity<GitPanel>, git_panel: Entity<GitPanel>,
commit_editor: Entity<Editor>, commit_editor: Entity<Editor>,
@ -108,7 +98,7 @@ struct RestoreDock {
} }
impl CommitModal { 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| { workspace.register_action(|workspace, _: &Commit, window, cx| {
CommitModal::toggle(workspace, window, cx); CommitModal::toggle(workspace, window, cx);
}); });

View file

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

View file

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

View file

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

View file

@ -9,14 +9,33 @@ use project::{
}; };
use std::sync::Arc; use std::sync::Arc;
use ui::{prelude::*, ListItem, ListItemSpacing}; 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 { pub struct RepositorySelector {
width: Rems,
picker: Entity<Picker<RepositorySelectorDelegate>>, picker: Entity<Picker<RepositorySelectorDelegate>>,
} }
impl RepositorySelector { impl RepositorySelector {
pub fn new( pub fn new(
project_handle: Entity<Project>, project_handle: Entity<Project>,
width: Rems,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
@ -48,7 +67,7 @@ impl RepositorySelector {
.max_height(Some(rems(20.).into())) .max_height(Some(rems(20.).into()))
}); });
RepositorySelector { picker } RepositorySelector { picker, width }
} }
} }
@ -91,10 +110,12 @@ impl Focusable for RepositorySelector {
impl Render for RepositorySelector { impl Render for RepositorySelector {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement { 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 { pub struct RepositorySelectorDelegate {
project: WeakEntity<Project>, project: WeakEntity<Project>,
repository_selector: WeakEntity<RepositorySelector>, repository_selector: WeakEntity<RepositorySelector>,

View file

@ -2039,7 +2039,9 @@ impl Repository {
let mut path = PathBuf::new(); let mut path = PathBuf::new();
path = path.join(worktree_name); 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()) Some(path.to_string_lossy().to_string())
}) })
.unwrap_or_else(|| self.repository_entry.work_directory.display_name()) .unwrap_or_else(|| self.repository_entry.work_directory.display_name())

View file

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