git_ui: Add some fixes and improvements to the worktree picker (#46407)

Danilo Leal created

This PR adds some clean up to the worktree picker: making sure that
worktree names render just one line, even when they might have multiple
lines (that was making each line item break for me), removing
superfluous icons, and making the footer controls more consistent with
the branch picker, where the buttons change to "Create..." if you're in
a state where the only match is the one to create a new worktree.


https://github.com/user-attachments/assets/e63aa8c9-b5a0-443d-a2ab-7759daed10d1

Release Notes:

- Git: Fixed some UI bugs in the worktree picker as well as improved the
UX for creating worktrees based off of the current or default branch.

Change summary

crates/git_ui/src/worktree_picker.rs | 196 +++++++++++++++--------------
1 file changed, 101 insertions(+), 95 deletions(-)

Detailed changes

crates/git_ui/src/worktree_picker.rs 🔗

@@ -18,7 +18,7 @@ use project::{
 use recent_projects::{RemoteConnectionModal, connect};
 use remote::{RemoteConnectionOptions, remote_client::ConnectionIdentifier};
 use std::{path::PathBuf, sync::Arc};
-use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
+use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, prelude::*};
 use util::ResultExt;
 use workspace::{ModalView, Workspace, notifications::DetachAndPromptErr};
 
@@ -632,68 +632,32 @@ impl PickerDelegate for WorktreeListDelegate {
             .take(7)
             .collect::<String>();
 
-        let focus_handle = self.focus_handle.clone();
-        let icon = if let Some(default_branch) = self.default_branch.clone()
-            && entry.is_new
-        {
-            Some(
-                IconButton::new("worktree-from-default", IconName::GitBranchAlt)
-                    .on_click(|_, window, cx| {
-                        window.dispatch_action(WorktreeFromDefault.boxed_clone(), cx)
-                    })
-                    .on_right_click(|_, window, cx| {
-                        window.dispatch_action(WorktreeFromDefaultOnWindow.boxed_clone(), cx)
-                    })
-                    .tooltip(move |_, cx| {
-                        Tooltip::for_action_in(
-                            format!("From default branch {default_branch}"),
-                            &WorktreeFromDefault,
-                            &focus_handle,
-                            cx,
-                        )
-                    }),
+        let (branch_name, sublabel) = if entry.is_new {
+            (
+                Label::new(format!("Create Worktree: \"{}\"…", entry.worktree.branch()))
+                    .truncate()
+                    .into_any_element(),
+                format!(
+                    "based off {}",
+                    self.base_branch(cx).unwrap_or("the current branch")
+                ),
             )
         } else {
-            None
-        };
-
-        let branch_name = if entry.is_new {
-            h_flex()
-                .gap_1()
-                .child(
-                    Icon::new(IconName::Plus)
-                        .size(IconSize::Small)
-                        .color(Color::Muted),
-                )
-                .child(
-                    Label::new(format!("Create worktree \"{}\"…", entry.worktree.branch()))
-                        .single_line()
-                        .truncate(),
-                )
-                .into_any_element()
-        } else {
-            h_flex()
-                .gap_1()
-                .child(
-                    Icon::new(IconName::GitBranch)
-                        .size(IconSize::Small)
-                        .color(Color::Muted),
-                )
-                .child(HighlightedLabel::new(
-                    entry.worktree.branch().to_owned(),
-                    entry.positions.clone(),
-                ))
-                .truncate()
-                .into_any_element()
-        };
-
-        let sublabel = if entry.is_new {
-            format!(
-                "based off {}",
-                self.base_branch(cx).unwrap_or("the current branch")
+            let branch = entry.worktree.branch();
+            let branch_first_line = branch.lines().next().unwrap_or(branch);
+            let positions: Vec<_> = entry
+                .positions
+                .iter()
+                .copied()
+                .filter(|&pos| pos < branch_first_line.len())
+                .collect();
+
+            (
+                HighlightedLabel::new(branch_first_line.to_owned(), positions)
+                    .truncate()
+                    .into_any_element(),
+                path,
             )
-        } else {
-            format!("at {}", path)
         };
 
         Some(
@@ -704,33 +668,30 @@ impl PickerDelegate for WorktreeListDelegate {
                 .child(
                     v_flex()
                         .w_full()
-                        .overflow_hidden()
                         .child(
                             h_flex()
-                                .gap_6()
+                                .gap_2()
                                 .justify_between()
                                 .overflow_x_hidden()
                                 .child(branch_name)
-                                .when(!entry.is_new, |el| {
-                                    el.child(
+                                .when(!entry.is_new, |this| {
+                                    this.child(
                                         Label::new(sha)
                                             .size(LabelSize::Small)
                                             .color(Color::Muted)
+                                            .buffer_font(cx)
                                             .into_element(),
                                     )
                                 }),
                         )
                         .child(
-                            div().max_w_96().child(
-                                Label::new(sublabel)
-                                    .size(LabelSize::Small)
-                                    .color(Color::Muted)
-                                    .truncate()
-                                    .into_any_element(),
-                            ),
+                            Label::new(sublabel)
+                                .size(LabelSize::Small)
+                                .color(Color::Muted)
+                                .truncate()
+                                .into_any_element(),
                         ),
-                )
-                .end_slot::<IconButton>(icon),
+                ),
         )
     }
 
@@ -740,17 +701,42 @@ impl PickerDelegate for WorktreeListDelegate {
 
     fn render_footer(&self, _: &mut Window, cx: &mut Context<Picker<Self>>) -> Option<AnyElement> {
         let focus_handle = self.focus_handle.clone();
+        let selected_entry = self.matches.get(self.selected_index);
+        let is_creating = selected_entry.is_some_and(|entry| entry.is_new);
+
+        let footer_container = h_flex()
+            .w_full()
+            .p_1p5()
+            .gap_0p5()
+            .justify_end()
+            .border_t_1()
+            .border_color(cx.theme().colors().border_variant);
+
+        if is_creating {
+            let from_default_button = self.default_branch.as_ref().map(|default_branch| {
+                Button::new(
+                    "worktree-from-default",
+                    format!("Create from: {default_branch}"),
+                )
+                .key_binding(
+                    KeyBinding::for_action_in(&WorktreeFromDefault, &focus_handle, cx)
+                        .map(|kb| kb.size(rems_from_px(12.))),
+                )
+                .on_click(|_, window, cx| {
+                    window.dispatch_action(WorktreeFromDefault.boxed_clone(), cx)
+                })
+            });
 
-        Some(
-            h_flex()
-                .w_full()
-                .p_1p5()
-                .gap_0p5()
-                .justify_end()
-                .border_t_1()
-                .border_color(cx.theme().colors().border_variant)
-                .child(
-                    Button::new("open-in-new-window", "Open in new window")
+            let current_branch = self.base_branch(cx).unwrap_or("current branch");
+
+            Some(
+                footer_container
+                    .when_some(from_default_button, |this, button| this.child(button))
+                    .child(
+                        Button::new(
+                            "worktree-from-current",
+                            format!("Create from: {current_branch}"),
+                        )
                         .key_binding(
                             KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
                                 .map(|kb| kb.size(rems_from_px(12.))),
@@ -758,18 +744,38 @@ impl PickerDelegate for WorktreeListDelegate {
                         .on_click(|_, window, cx| {
                             window.dispatch_action(menu::Confirm.boxed_clone(), cx)
                         }),
-                )
-                .child(
-                    Button::new("open-in-window", "Open")
-                        .key_binding(
-                            KeyBinding::for_action_in(&menu::SecondaryConfirm, &focus_handle, cx)
+                    )
+                    .into_any(),
+            )
+        } else {
+            Some(
+                footer_container
+                    .child(
+                        Button::new("open-in-new-window", "Open in New Window")
+                            .key_binding(
+                                KeyBinding::for_action_in(&menu::Confirm, &focus_handle, cx)
+                                    .map(|kb| kb.size(rems_from_px(12.))),
+                            )
+                            .on_click(|_, window, cx| {
+                                window.dispatch_action(menu::Confirm.boxed_clone(), cx)
+                            }),
+                    )
+                    .child(
+                        Button::new("open-in-window", "Open")
+                            .key_binding(
+                                KeyBinding::for_action_in(
+                                    &menu::SecondaryConfirm,
+                                    &focus_handle,
+                                    cx,
+                                )
                                 .map(|kb| kb.size(rems_from_px(12.))),
-                        )
-                        .on_click(|_, window, cx| {
-                            window.dispatch_action(menu::SecondaryConfirm.boxed_clone(), cx)
-                        }),
-                )
-                .into_any(),
-        )
+                            )
+                            .on_click(|_, window, cx| {
+                                window.dispatch_action(menu::SecondaryConfirm.boxed_clone(), cx)
+                            }),
+                    )
+                    .into_any(),
+            )
+        }
     }
 }