diff --git a/assets/icons/box_open.svg b/assets/icons/box_open.svg
new file mode 100644
index 0000000000000000000000000000000000000000..5e30fc40c3446485412e2a2607b0d07dc2f68b4b
--- /dev/null
+++ b/assets/icons/box_open.svg
@@ -0,0 +1,6 @@
+
diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs
index cfb7b6bd2a2fb6c57f17244e0e57a4a637866418..d9adc079bf470e121c99b2d13c2ef25ffea7a68f 100644
--- a/crates/git_ui/src/branch_picker.rs
+++ b/crates/git_ui/src/branch_picker.rs
@@ -559,8 +559,7 @@ impl PickerDelegate for BranchListDelegate {
match self.state {
PickerState::List | PickerState::NewRemote | PickerState::NewBranch => {
match self.branch_filter {
- BranchFilter::All => "Select branch or remote…",
- BranchFilter::Remote => "Select remote…",
+ BranchFilter::All | BranchFilter::Remote => "Select branch…",
}
}
PickerState::CreateRemote(_) => "Enter a name for this remote…",
@@ -884,13 +883,13 @@ impl PickerDelegate for BranchListDelegate {
let entry_icon = match entry {
Entry::NewUrl { .. } | Entry::NewBranch { .. } | Entry::NewRemoteName { .. } => {
- Icon::new(IconName::Plus).color(Color::Muted)
+ IconName::Plus
}
Entry::Branch { branch, .. } => {
if branch.is_remote() {
- Icon::new(IconName::Screen).color(Color::Muted)
+ IconName::Screen
} else {
- Icon::new(IconName::GitBranchAlt).color(Color::Muted)
+ IconName::GitBranchAlt
}
}
};
@@ -922,8 +921,11 @@ impl PickerDelegate for BranchListDelegate {
Entry::NewUrl { .. } | Entry::NewBranch { .. } | Entry::NewRemoteName { .. }
);
- let deleted_branch_icon = |entry_ix: usize, is_head_branch: bool| {
+ let is_head_branch = entry.as_branch().is_some_and(|branch| branch.is_head);
+
+ let deleted_branch_icon = |entry_ix: usize| {
IconButton::new(("delete", entry_ix), IconName::Trash)
+ .icon_size(IconSize::Small)
.tooltip(move |_, cx| {
Tooltip::for_action_in(
"Delete Branch",
@@ -932,7 +934,6 @@ impl PickerDelegate for BranchListDelegate {
cx,
)
})
- .disabled(is_head_branch)
.on_click(cx.listener(move |this, _, window, cx| {
this.delegate.delete_at(entry_ix, window, cx);
}))
@@ -943,6 +944,7 @@ impl PickerDelegate for BranchListDelegate {
let focus_handle = self.focus_handle.clone();
IconButton::new("create_from_default", IconName::GitBranchPlus)
+ .icon_size(IconSize::Small)
.tooltip(move |_, cx| {
Tooltip::for_action_in(
tooltip_label.clone(),
@@ -965,105 +967,132 @@ impl PickerDelegate for BranchListDelegate {
.child(
h_flex()
.w_full()
- .gap_3()
+ .gap_2p5()
.flex_grow()
- .child(entry_icon)
+ .child(
+ Icon::new(entry_icon)
+ .color(Color::Muted)
+ .size(IconSize::Small),
+ )
.child(
v_flex()
.id("info_container")
.w_full()
.child(entry_title)
- .child(
- h_flex()
- .w_full()
- .justify_between()
- .gap_1p5()
- .when(self.style == BranchListStyle::Modal, |el| {
- el.child(div().max_w_96().child({
- let message = match entry {
- Entry::NewUrl { url } => {
- format!("Based off {url}")
- }
- Entry::NewRemoteName { url, .. } => {
- format!("Based off {url}")
- }
- Entry::NewBranch { .. } => {
- if let Some(current_branch) =
- self.repo.as_ref().and_then(|repo| {
- repo.read(cx)
- .branch
- .as_ref()
- .map(|b| b.name())
- })
- {
- format!("Based off {}", current_branch)
- } else {
- "Based off the current branch"
- .to_string()
- }
- }
- Entry::Branch { .. } => {
- let show_author_name =
- ProjectSettings::get_global(cx)
- .git
- .branch_picker
- .show_author_name;
-
- subject.map_or(
- "No commits found".into(),
- |subject| {
- if show_author_name
- && let Some(author) =
- author_name
- {
- format!(
- "{} • {}",
- author, subject
- )
- } else {
- subject.to_string()
- }
- },
- )
- }
- };
-
- Label::new(message)
- .size(LabelSize::Small)
- .color(Color::Muted)
- .truncate()
- }))
- })
- .when_some(commit_time, |label, commit_time| {
- label.child(
- Label::new(commit_time)
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- }),
- )
+ .child({
+ let message = match entry {
+ Entry::NewUrl { url } => format!("Based off {url}"),
+ Entry::NewRemoteName { url, .. } => {
+ format!("Based off {url}")
+ }
+ Entry::NewBranch { .. } => {
+ if let Some(current_branch) =
+ self.repo.as_ref().and_then(|repo| {
+ repo.read(cx).branch.as_ref().map(|b| b.name())
+ })
+ {
+ format!("Based off {}", current_branch)
+ } else {
+ "Based off the current branch".to_string()
+ }
+ }
+ Entry::Branch { .. } => String::new(),
+ };
+
+ if matches!(entry, Entry::Branch { .. }) {
+ let show_author_name = ProjectSettings::get_global(cx)
+ .git
+ .branch_picker
+ .show_author_name;
+ let has_author = show_author_name && author_name.is_some();
+ let has_commit = commit_time.is_some();
+ let author_for_meta =
+ if show_author_name { author_name } else { None };
+
+ let dot = || {
+ Label::new("•")
+ .alpha(0.5)
+ .color(Color::Muted)
+ .size(LabelSize::Small)
+ };
+
+ h_flex()
+ .w_full()
+ .min_w_0()
+ .gap_1p5()
+ .when_some(author_for_meta, |this, author| {
+ this.child(
+ Label::new(author)
+ .color(Color::Muted)
+ .size(LabelSize::Small),
+ )
+ })
+ .when_some(commit_time, |this, time| {
+ this.when(has_author, |this| this.child(dot()))
+ .child(
+ Label::new(time)
+ .color(Color::Muted)
+ .size(LabelSize::Small),
+ )
+ })
+ .when_some(subject, |this, subj| {
+ this.when(has_commit, |this| this.child(dot()))
+ .child(
+ Label::new(subj.to_string())
+ .color(Color::Muted)
+ .size(LabelSize::Small)
+ .truncate()
+ .flex_1(),
+ )
+ })
+ .when(!has_commit, |this| {
+ this.child(
+ Label::new("No commits found")
+ .color(Color::Muted)
+ .size(LabelSize::Small),
+ )
+ })
+ .into_any_element()
+ } else {
+ Label::new(message)
+ .size(LabelSize::Small)
+ .color(Color::Muted)
+ .truncate()
+ .into_any_element()
+ }
+ })
.when_some(
entry.as_branch().map(|b| b.name().to_string()),
- |this, branch_name| this.tooltip(Tooltip::text(branch_name)),
+ |this, branch_name| {
+ this.map(|this| {
+ if is_head_branch {
+ this.tooltip(move |_, cx| {
+ Tooltip::with_meta(
+ branch_name.clone(),
+ None,
+ "Current Branch",
+ cx,
+ )
+ })
+ } else {
+ this.tooltip(Tooltip::text(branch_name))
+ }
+ })
+ },
),
),
)
- .when(
- self.editor_position() == PickerEditorPosition::End && !is_new_items,
- |this| {
- this.map(|this| {
- let is_head_branch =
- entry.as_branch().is_some_and(|branch| branch.is_head);
- if self.selected_index() == ix {
- this.end_slot(deleted_branch_icon(ix, is_head_branch))
- } else {
- this.end_hover_slot(deleted_branch_icon(ix, is_head_branch))
- }
- })
- },
- )
+ .when(!is_new_items && !is_head_branch, |this| {
+ this.map(|this| {
+ if self.selected_index() == ix {
+ this.end_slot(deleted_branch_icon(ix))
+ } else {
+ this.end_hover_slot(deleted_branch_icon(ix))
+ }
+ })
+ })
.when_some(
- if self.editor_position() == PickerEditorPosition::End && is_new_items {
+ if is_new_items {
create_from_default_button
} else {
None
diff --git a/crates/git_ui/src/stash_picker.rs b/crates/git_ui/src/stash_picker.rs
index e736dd806a35703991e1fb51e27e3952e5692d99..8947ed233790ab65557e13518d51bd383fc93c2d 100644
--- a/crates/git_ui/src/stash_picker.rs
+++ b/crates/git_ui/src/stash_picker.rs
@@ -468,7 +468,7 @@ impl PickerDelegate for StashListDelegate {
ix: usize,
selected: bool,
_window: &mut Window,
- _cx: &mut Context>,
+ cx: &mut Context>,
) -> Option {
let entry_match = &self.matches[ix];
@@ -501,16 +501,46 @@ impl PickerDelegate for StashListDelegate {
.size(LabelSize::Small),
);
+ let focus_handle = self.focus_handle.clone();
+
+ let drop_button = |entry_ix: usize| {
+ IconButton::new(("drop-stash", entry_ix), IconName::Trash)
+ .icon_size(IconSize::Small)
+ .tooltip(move |_, cx| {
+ Tooltip::for_action_in("Drop Stash", &DropStashItem, &focus_handle, cx)
+ })
+ .on_click(cx.listener(move |this, _, window, cx| {
+ this.delegate.drop_stash_at(entry_ix, window, cx);
+ }))
+ };
+
Some(
ListItem::new(format!("stash-{ix}"))
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
- .child(v_flex().w_full().child(stash_label).child(branch_info))
+ .child(
+ h_flex()
+ .w_full()
+ .gap_2p5()
+ .child(
+ Icon::new(IconName::BoxOpen)
+ .size(IconSize::Small)
+ .color(Color::Muted),
+ )
+ .child(div().w_full().child(stash_label).child(branch_info)),
+ )
.tooltip(Tooltip::text(format!(
"stash@{{{}}}",
entry_match.entry.index
- ))),
+ )))
+ .map(|this| {
+ if selected {
+ this.end_slot(drop_button(ix))
+ } else {
+ this.end_hover_slot(drop_button(ix))
+ }
+ }),
)
}
diff --git a/crates/git_ui/src/worktree_picker.rs b/crates/git_ui/src/worktree_picker.rs
index 3806054038151c3dd80a7bb1cfdedf966d4219b5..2cbba9347f973aa084413b03af7314f30d22fc99 100644
--- a/crates/git_ui/src/worktree_picker.rs
+++ b/crates/git_ui/src/worktree_picker.rs
@@ -18,7 +18,7 @@ use remote::{RemoteConnectionOptions, remote_client::ConnectionIdentifier};
use remote_connection::{RemoteConnectionModal, connect};
use settings::Settings;
use std::{path::PathBuf, sync::Arc};
-use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, prelude::*};
+use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
use util::ResultExt;
use workspace::{ModalView, MultiWorkspace, Workspace, notifications::DetachAndPromptErr};
@@ -769,38 +769,83 @@ impl PickerDelegate for WorktreeListDelegate {
)
};
+ let focus_handle = self.focus_handle.clone();
+
+ let delete_button = |entry_ix: usize| {
+ IconButton::new(("delete-worktree", entry_ix), IconName::Trash)
+ .icon_size(IconSize::Small)
+ .tooltip(move |_, cx| {
+ Tooltip::for_action_in("Delete Worktree", &DeleteWorktree, &focus_handle, cx)
+ })
+ .on_click(cx.listener(move |this, _, window, cx| {
+ this.delegate.delete_at(entry_ix, window, cx);
+ }))
+ };
+
+ let entry_icon = if entry.is_new {
+ IconName::Plus
+ } else {
+ IconName::GitWorktree
+ };
+
Some(
ListItem::new(format!("worktree-menu-{ix}"))
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.child(
- v_flex()
+ h_flex()
.w_full()
+ .gap_2p5()
.child(
- h_flex()
- .gap_2()
- .justify_between()
- .overflow_x_hidden()
- .child(branch_name)
- .when(!entry.is_new, |this| {
- this.child(
- Label::new(sha)
- .size(LabelSize::Small)
- .color(Color::Muted)
- .buffer_font(cx)
- .into_element(),
- )
- }),
- )
- .child(
- Label::new(sublabel)
- .size(LabelSize::Small)
+ Icon::new(entry_icon)
.color(Color::Muted)
- .truncate()
- .into_any_element(),
- ),
- ),
+ .size(IconSize::Small),
+ )
+ .child(v_flex().w_full().child(branch_name).map(|this| {
+ if entry.is_new {
+ this.child(
+ Label::new(sublabel)
+ .size(LabelSize::Small)
+ .color(Color::Muted)
+ .truncate(),
+ )
+ } else {
+ this.child(
+ h_flex()
+ .w_full()
+ .min_w_0()
+ .gap_1p5()
+ .child(
+ Label::new(sha)
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ )
+ .child(
+ Label::new("•")
+ .alpha(0.5)
+ .color(Color::Muted)
+ .size(LabelSize::Small),
+ )
+ .child(
+ Label::new(sublabel)
+ .truncate()
+ .color(Color::Muted)
+ .size(LabelSize::Small)
+ .flex_1(),
+ )
+ .into_any_element(),
+ )
+ }
+ })),
+ )
+ .when(!entry.is_new, |this| {
+ if selected {
+ this.end_slot(delete_button(ix))
+ } else {
+ this.end_hover_slot(delete_button(ix))
+ }
+ }),
)
}
diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs
index bcdd156e6ef7ab55770598eda8e4a2e7f0ae530b..625b51f9c1b9f5cbf04f4474b72d08557542352f 100644
--- a/crates/icons/src/icons.rs
+++ b/crates/icons/src/icons.rs
@@ -53,6 +53,7 @@ pub enum IconName {
Book,
BookCopy,
Box,
+ BoxOpen,
CaseSensitive,
Chat,
Check,