git_panel: Add Git actions in split button (#59608)
# Objective Improve the Git panel changes header by making the Stage All / Unstage All control more consistent with other split-button actions in the Git panel, such as the Fetch/Pull button. ## Solution - Replaced the standalone Stage All / Unstage All button with a split button. - Kept the primary action context-sensitive, switching between Stage All and Unstage All. - Moved related Git change actions into the split-button menu: staging, stashing, discard tracked changes, and trash untracked files. ## Testing - Ran `cargo fmt --check`. - Ran `cargo check -p git_ui`. - Manually verified the Git panel dropdown behavior in the app. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase The Git panel changes header now uses a split button similar to the Fetch/Pull control. The primary action updates between Stage All and Unstage All, while the dropdown groups related Git actions. when nothing is staged <img width="359" height="245" alt="Screenshot 2026-06-19 at 11 01 44 PM" src="https://github.com/user-attachments/assets/7fb7601f-8396-49c7-9d90-9f2e1706230a" /> when everything is staged already <img width="360" height="219" alt="Screenshot 2026-06-19 at 11 01 52 PM" src="https://github.com/user-attachments/assets/38ebebd9-09c2-4d24-aac3-6c4e22786ba5" /> rest of the menu options <img width="385" height="294" alt="Screenshot 2026-06-19 at 11 01 59 PM" src="https://github.com/user-attachments/assets/47393a17-78af-411c-9cb1-90781cf3a645" /> --- Release Notes: - Improved Git panel change actions with a split-button menu. --------- Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
This commit is contained in:
1 parent
fdfc80ca83
commit
7e11793875
1 file changed
+98
-108
+98
-108
@@ -3,7 +3,7 @@ use crate::commit_modal::CommitModal;
|
||||
use crate::commit_tooltip::{CommitAvatar, CommitTooltip};
|
||||
use crate::commit_view::CommitView;
|
||||
use crate::git_panel_settings::GitPanelScrollbarAccessor;
|
||||
use crate::project_diff::{self, BranchDiff, Diff, ProjectDiff};
|
||||
use crate::project_diff::{BranchDiff, Diff, ProjectDiff};
|
||||
use crate::remote_output::{self, RemoteAction, SuccessMessage};
|
||||
use crate::solo_diff_view::SoloDiffView;
|
||||
use crate::{branch_picker, picker_prompt, render_remote_button};
|
||||
@@ -163,53 +163,42 @@ enum TrashCancel {
|
||||
}
|
||||
|
||||
struct GitMenuState {
|
||||
has_tracked_changes: bool,
|
||||
has_staged_changes: bool,
|
||||
has_unstaged_changes: bool,
|
||||
has_new_changes: bool,
|
||||
sort_by: GitPanelSortBy,
|
||||
group_by: GitPanelGroupBy,
|
||||
has_stash_items: bool,
|
||||
tree_view: bool,
|
||||
}
|
||||
|
||||
fn git_panel_context_menu(
|
||||
has_tracked_changes: bool,
|
||||
has_staged_changes: bool,
|
||||
has_unstaged_changes: bool,
|
||||
has_new_changes: bool,
|
||||
has_stash_items: bool,
|
||||
focus_handle: FocusHandle,
|
||||
state: GitMenuState,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<ContextMenu> {
|
||||
ContextMenu::build(window, cx, move |context_menu, _, _| {
|
||||
ContextMenu::build(window, cx, |context_menu, _, _| {
|
||||
context_menu
|
||||
.context(focus_handle)
|
||||
.action_disabled_when(
|
||||
!state.has_unstaged_changes,
|
||||
"Stage All",
|
||||
StageAll.boxed_clone(),
|
||||
)
|
||||
.action_disabled_when(
|
||||
!state.has_staged_changes,
|
||||
"Unstage All",
|
||||
UnstageAll.boxed_clone(),
|
||||
)
|
||||
.context(focus_handle.clone())
|
||||
.action_disabled_when(!has_unstaged_changes, "Stage All", StageAll.boxed_clone())
|
||||
.action_disabled_when(!has_staged_changes, "Unstage All", UnstageAll.boxed_clone())
|
||||
.separator()
|
||||
.action_disabled_when(
|
||||
!(state.has_new_changes || state.has_tracked_changes),
|
||||
!(has_new_changes || has_tracked_changes),
|
||||
"Stash All",
|
||||
StashAll.boxed_clone(),
|
||||
)
|
||||
.action_disabled_when(!state.has_stash_items, "Stash Pop", StashPop.boxed_clone())
|
||||
.action_disabled_when(!has_stash_items, "Stash Pop", StashPop.boxed_clone())
|
||||
.action("View Stash", zed_actions::git::ViewStash.boxed_clone())
|
||||
.separator()
|
||||
.action("Open Diff", project_diff::Diff.boxed_clone())
|
||||
.separator()
|
||||
.action_disabled_when(
|
||||
!state.has_tracked_changes,
|
||||
!has_tracked_changes,
|
||||
"Discard Tracked Changes",
|
||||
RestoreTrackedFiles.boxed_clone(),
|
||||
)
|
||||
.action_disabled_when(
|
||||
!state.has_new_changes,
|
||||
!has_new_changes,
|
||||
"Trash Untracked Files",
|
||||
TrashUntrackedFiles.boxed_clone(),
|
||||
)
|
||||
@@ -4410,46 +4399,8 @@ impl GitPanel {
|
||||
path + file_name + depth * 2
|
||||
}
|
||||
|
||||
fn render_ellipsis_menu(&self, id: impl Into<ElementId>) -> impl IntoElement {
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let has_tracked_changes = self.has_tracked_changes();
|
||||
let has_staged_changes = self.has_staged_changes();
|
||||
let has_unstaged_changes = self.has_unstaged_changes();
|
||||
let has_new_changes = self.new_count > 0;
|
||||
let has_stash_items = self.stash_entries.entries.len() > 0;
|
||||
|
||||
PopoverMenu::new(id.into())
|
||||
.trigger(
|
||||
IconButton::new("overflow-menu-trigger", IconName::Ellipsis)
|
||||
.icon_size(IconSize::Small),
|
||||
)
|
||||
.menu(move |window, cx| {
|
||||
Some(git_panel_context_menu(
|
||||
focus_handle.clone(),
|
||||
GitMenuState {
|
||||
has_tracked_changes,
|
||||
has_staged_changes,
|
||||
has_unstaged_changes,
|
||||
has_new_changes,
|
||||
sort_by: GitPanelSettings::get_global(cx).sort_by,
|
||||
group_by: GitPanelSettings::get_global(cx).group_by,
|
||||
has_stash_items,
|
||||
tree_view: GitPanelSettings::get_global(cx).tree_view,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
})
|
||||
.anchor(Anchor::TopRight)
|
||||
}
|
||||
|
||||
fn render_view_options_menu(&self, id: impl Into<ElementId>) -> impl IntoElement {
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let has_tracked_changes = self.has_tracked_changes();
|
||||
let has_staged_changes = self.has_staged_changes();
|
||||
let has_unstaged_changes = self.has_unstaged_changes();
|
||||
let has_new_changes = self.new_count > 0;
|
||||
let has_stash_items = self.stash_entries.entries.len() > 0;
|
||||
|
||||
PopoverMenu::new(id.into())
|
||||
.trigger(
|
||||
@@ -4461,13 +4412,8 @@ impl GitPanel {
|
||||
Some(git_panel_view_options_menu(
|
||||
focus_handle.clone(),
|
||||
GitMenuState {
|
||||
has_tracked_changes,
|
||||
has_staged_changes,
|
||||
has_unstaged_changes,
|
||||
has_new_changes,
|
||||
sort_by: GitPanelSettings::get_global(cx).sort_by,
|
||||
group_by: GitPanelSettings::get_global(cx).group_by,
|
||||
has_stash_items,
|
||||
tree_view: GitPanelSettings::get_global(cx).tree_view,
|
||||
},
|
||||
window,
|
||||
@@ -4727,6 +4673,78 @@ impl GitPanel {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_git_changes_actions_menu(
|
||||
&self,
|
||||
id: impl Into<ElementId>,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let has_tracked_changes = self.has_tracked_changes();
|
||||
let has_staged_changes = self.has_staged_changes();
|
||||
let has_unstaged_changes = self.has_unstaged_changes();
|
||||
let has_new_changes = self.new_count > 0;
|
||||
let has_stash_items = self.stash_entries.entries.len() > 0;
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
PopoverMenu::new(id.into())
|
||||
.trigger(
|
||||
ui::ButtonLike::new_rounded_right("git-changes-actions-split-button-right")
|
||||
.layer(ui::ElevationIndex::ModalSurface)
|
||||
.size(ButtonSize::None)
|
||||
.child(
|
||||
div()
|
||||
.px_1()
|
||||
.child(Icon::new(IconName::ChevronDown).size(IconSize::XSmall)),
|
||||
),
|
||||
)
|
||||
.menu(move |window, cx| {
|
||||
Some(git_panel_context_menu(
|
||||
has_tracked_changes,
|
||||
has_staged_changes,
|
||||
has_unstaged_changes,
|
||||
has_new_changes,
|
||||
has_stash_items,
|
||||
focus_handle.clone(),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
})
|
||||
.anchor(Anchor::TopRight)
|
||||
}
|
||||
|
||||
fn render_git_changes_actions_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (text, action, stage, tooltip) =
|
||||
if self.total_staged_count() == self.entry_count && self.entry_count > 0 {
|
||||
("Unstage All", UnstageAll.boxed_clone(), false, "git reset")
|
||||
} else {
|
||||
("Stage All", StageAll.boxed_clone(), true, "git add --all")
|
||||
};
|
||||
|
||||
SplitButton::new(
|
||||
ButtonLike::new_rounded_left("git-changes-actions-split-button-left")
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.size(ButtonSize::Compact)
|
||||
.child(Label::new(text).size(LabelSize::Small).mr_0p5())
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
tooltip,
|
||||
action.as_ref(),
|
||||
&self.focus_handle,
|
||||
))
|
||||
.disabled(self.entry_count == 0)
|
||||
.on_click({
|
||||
let git_panel = cx.weak_entity();
|
||||
move |_, _, cx| {
|
||||
git_panel
|
||||
.update(cx, |git_panel, cx| {
|
||||
git_panel.change_all_files_stage(stage, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}),
|
||||
self.render_git_changes_actions_menu("git-changes-actions-split-button-menu", cx)
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_changes_header(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
@@ -4738,13 +4756,6 @@ impl GitPanel {
|
||||
|
||||
self.active_repository.as_ref()?;
|
||||
|
||||
let (text, action, stage, tooltip) =
|
||||
if self.total_staged_count() == self.entry_count && self.entry_count > 0 {
|
||||
("Unstage All", UnstageAll.boxed_clone(), false, "git reset")
|
||||
} else {
|
||||
("Stage All", StageAll.boxed_clone(), true, "git add --all")
|
||||
};
|
||||
|
||||
let diff_stat_total = self.diff_stat_total;
|
||||
|
||||
Some(
|
||||
@@ -4796,29 +4807,7 @@ impl GitPanel {
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(self.render_view_options_menu("view_options_menu"))
|
||||
.child(self.render_ellipsis_menu("overflow_menu"))
|
||||
.child(
|
||||
Button::new("stage_unstage_all", text)
|
||||
.label_size(LabelSize::Small)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.style(ButtonStyle::Filled)
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
tooltip,
|
||||
action.as_ref(),
|
||||
&self.focus_handle,
|
||||
))
|
||||
.disabled(self.entry_count == 0)
|
||||
.on_click({
|
||||
let git_panel = cx.weak_entity();
|
||||
move |_, _, cx| {
|
||||
git_panel
|
||||
.update(cx, |git_panel, cx| {
|
||||
git_panel.change_all_files_stage(stage, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}),
|
||||
),
|
||||
.child(self.render_git_changes_actions_button(cx)),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -6232,18 +6221,19 @@ impl GitPanel {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let has_tracked_changes = self.has_tracked_changes();
|
||||
let has_staged_changes = self.has_staged_changes();
|
||||
let has_unstaged_changes = self.has_unstaged_changes();
|
||||
let has_new_changes = self.new_count > 0;
|
||||
let has_stash_items = self.stash_entries.entries.len() > 0;
|
||||
|
||||
let context_menu = git_panel_context_menu(
|
||||
has_tracked_changes,
|
||||
has_staged_changes,
|
||||
has_unstaged_changes,
|
||||
has_new_changes,
|
||||
has_stash_items,
|
||||
self.focus_handle.clone(),
|
||||
GitMenuState {
|
||||
has_tracked_changes: self.has_tracked_changes(),
|
||||
has_staged_changes: self.has_staged_changes(),
|
||||
has_unstaged_changes: self.has_unstaged_changes(),
|
||||
has_new_changes: self.new_count > 0,
|
||||
sort_by: GitPanelSettings::get_global(cx).sort_by,
|
||||
group_by: GitPanelSettings::get_global(cx).group_by,
|
||||
has_stash_items: self.stash_entries.entries.len() > 0,
|
||||
tree_view: GitPanelSettings::get_global(cx).tree_view,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user