Vcs menu2 (#3648)

Piotr Osiewicz created

Header and footer are gonna be added in a separate PR as they require
changes to Picker trait that I feel are separate from the contents of
this PR.

Release Notes:

- N/A

Change summary

Cargo.lock                                    |  15 
Cargo.toml                                    |   1 
crates/collab_ui2/Cargo.toml                  |   2 
crates/collab_ui2/src/collab_titlebar_item.rs | 198 +++--------
crates/collab_ui2/src/collab_ui.rs            |   2 
crates/vcs_menu2/Cargo.toml                   |  17 
crates/vcs_menu2/src/lib.rs                   | 359 +++++++++++++++++++++
7 files changed, 446 insertions(+), 148 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1982,6 +1982,7 @@ dependencies = [
  "tree-sitter-markdown",
  "ui2",
  "util",
+ "vcs_menu2",
  "workspace2",
  "zed_actions2",
 ]
@@ -10873,6 +10874,20 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "vcs_menu2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "fs2",
+ "fuzzy2",
+ "gpui2",
+ "picker2",
+ "ui2",
+ "util",
+ "workspace2",
+]
+
 [[package]]
 name = "version_check"
 version = "0.9.4"

Cargo.toml 🔗

@@ -123,6 +123,7 @@ members = [
     "crates/story",
     "crates/vim",
     "crates/vcs_menu",
+    "crates/vcs_menu2",
     "crates/workspace2",
     "crates/welcome",
     "crates/welcome2",

crates/collab_ui2/Cargo.toml 🔗

@@ -47,7 +47,7 @@ settings = { package = "settings2", path = "../settings2" }
 feature_flags = { package = "feature_flags2", path = "../feature_flags2"}
 theme = { package = "theme2", path = "../theme2" }
 # theme_selector = { path = "../theme_selector" }
-# vcs_menu = { path = "../vcs_menu" }
+vcs_menu = { package = "vcs_menu2", path = "../vcs_menu2" }
 ui = { package = "ui2", path = "../ui2" }
 util = { path = "../util" }
 workspace = { package = "workspace2", path = "../workspace2" }

crates/collab_ui2/src/collab_titlebar_item.rs 🔗

@@ -2,10 +2,10 @@ use crate::face_pile::FacePile;
 use call::{ActiveCall, ParticipantLocation, Room};
 use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore};
 use gpui::{
-    actions, canvas, div, overlay, point, px, rems, AppContext, DismissEvent, Div, Element,
-    FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, Render,
-    Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext,
-    WeakView, WindowBounds,
+    actions, canvas, div, overlay, point, px, rems, AnyElement, AppContext, DismissEvent, Div,
+    Element, FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path,
+    Render, Stateful, StatefulInteractiveElement, Styled, Subscription, View, ViewContext,
+    VisualContext, WeakView, WindowBounds,
 };
 use project::{Project, RepositoryEntry};
 use recent_projects::RecentProjects;
@@ -13,9 +13,10 @@ use std::sync::Arc;
 use theme::{ActiveTheme, PlayerColors};
 use ui::{
     h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon,
-    IconButton, IconElement, KeyBinding, Tooltip,
+    IconButton, IconElement, Tooltip,
 };
 use util::ResultExt;
+use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu};
 use workspace::{notifications::NotifyResultExt, Workspace, WORKSPACE_DB};
 
 const MAX_PROJECT_NAME_LENGTH: usize = 40;
@@ -50,7 +51,7 @@ pub struct CollabTitlebarItem {
     user_store: Model<UserStore>,
     client: Arc<Client>,
     workspace: WeakView<Workspace>,
-    //branch_popover: Option<ViewHandle<BranchList>>,
+    branch_popover: Option<View<BranchList>>,
     project_popover: Option<recent_projects::RecentProjects>,
     //user_menu: ViewHandle<ContextMenu>,
     _subscriptions: Vec<Subscription>,
@@ -329,7 +330,7 @@ impl CollabTitlebarItem {
             //             menu.set_position_mode(OverlayPositionMode::Local);
             //             menu
             //         }),
-            //         branch_popover: None,
+            branch_popover: None,
             project_popover: None,
             _subscriptions: subscriptions,
         }
@@ -408,23 +409,25 @@ impl CollabTitlebarItem {
             .map(|branch| util::truncate_and_trailoff(&branch, MAX_BRANCH_NAME_LENGTH))?;
 
         Some(
-            div().border().border_color(gpui::red()).child(
-                Button::new("project_branch_trigger", branch_name)
-                    .style(ButtonStyle::Subtle)
-                    .tooltip(move |cx| {
-                        cx.build_view(|_| {
-                            Tooltip::new("Recent Branches")
-                                .key_binding(KeyBinding::new(gpui::KeyBinding::new(
-                                    "cmd-b",
-                                    // todo!() Replace with real action.
-                                    gpui::NoAction,
-                                    None,
-                                )))
-                                .meta("Local branches only")
+            div()
+                .border()
+                .border_color(gpui::red())
+                .child(
+                    Button::new("project_branch_trigger", branch_name)
+                        .style(ButtonStyle::Subtle)
+                        .tooltip(move |cx| {
+                            Tooltip::with_meta(
+                                "Recent Branches",
+                                Some(&ToggleVcsMenu),
+                                "Local branches only",
+                                cx,
+                            )
                         })
-                        .into()
-                    }),
-            ),
+                        .on_click(
+                            cx.listener(|this, _, cx| this.toggle_vcs_menu(&ToggleVcsMenu, cx)),
+                        ),
+                )
+                .children(self.render_branches_popover_host()),
         )
     }
 
@@ -503,131 +506,34 @@ impl CollabTitlebarItem {
             .log_err();
     }
 
-    // pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
-    //     self.user_menu.update(cx, |user_menu, cx| {
-    //         let items = if let Some(_) = self.user_store.read(cx).current_user() {
-    //             vec![
-    //                 ContextMenuItem::action("Settings", zed_actions::OpenSettings),
-    //                 ContextMenuItem::action("Theme", theme_selector::Toggle),
-    //                 ContextMenuItem::separator(),
-    //                 ContextMenuItem::action(
-    //                     "Share Feedback",
-    //                     feedback::feedback_editor::GiveFeedback,
-    //                 ),
-    //                 ContextMenuItem::action("Sign Out", SignOut),
-    //             ]
-    //         } else {
-    //             vec![
-    //                 ContextMenuItem::action("Settings", zed_actions::OpenSettings),
-    //                 ContextMenuItem::action("Theme", theme_selector::Toggle),
-    //                 ContextMenuItem::separator(),
-    //                 ContextMenuItem::action(
-    //                     "Share Feedback",
-    //                     feedback::feedback_editor::GiveFeedback,
-    //                 ),
-    //             ]
-    //         };
-    //         user_menu.toggle(Default::default(), AnchorCorner::TopRight, items, cx);
-    //     });
-    // }
-
-    // fn render_branches_popover_host<'a>(
-    //     &'a self,
-    //     _theme: &'a theme::Titlebar,
-    //     cx: &'a mut ViewContext<Self>,
-    // ) -> Option<AnyElement<Self>> {
-    //     self.branch_popover.as_ref().map(|child| {
-    //         let theme = theme::current(cx).clone();
-    //         let child = ChildView::new(child, cx);
-    //         let child = MouseEventHandler::new::<BranchList, _>(0, cx, |_, _| {
-    //             child
-    //                 .flex(1., true)
-    //                 .contained()
-    //                 .constrained()
-    //                 .with_width(theme.titlebar.menu.width)
-    //                 .with_height(theme.titlebar.menu.height)
-    //         })
-    //         .on_click(MouseButton::Left, |_, _, _| {})
-    //         .on_down_out(MouseButton::Left, move |_, this, cx| {
-    //             this.branch_popover.take();
-    //             cx.emit(());
-    //             cx.notify();
-    //         })
-    //         .contained()
-    //         .into_any();
-
-    //         Overlay::new(child)
-    //             .with_fit_mode(OverlayFitMode::SwitchAnchor)
-    //             .with_anchor_corner(AnchorCorner::TopLeft)
-    //             .with_z_index(999)
-    //             .aligned()
-    //             .bottom()
-    //             .left()
-    //             .into_any()
-    //     })
-    // }
-
-    // fn render_project_popover_host<'a>(
-    //     &'a self,
-    //     _theme: &'a theme::Titlebar,
-    //     cx: &'a mut ViewContext<Self>,
-    // ) -> Option<AnyElement<Self>> {
-    //     self.project_popover.as_ref().map(|child| {
-    //         let theme = theme::current(cx).clone();
-    //         let child = ChildView::new(child, cx);
-    //         let child = MouseEventHandler::new::<RecentProjects, _>(0, cx, |_, _| {
-    //             child
-    //                 .flex(1., true)
-    //                 .contained()
-    //                 .constrained()
-    //                 .with_width(theme.titlebar.menu.width)
-    //                 .with_height(theme.titlebar.menu.height)
-    //         })
-    //         .on_click(MouseButton::Left, |_, _, _| {})
-    //         .on_down_out(MouseButton::Left, move |_, this, cx| {
-    //             this.project_popover.take();
-    //             cx.emit(());
-    //             cx.notify();
-    //         })
-    //         .into_any();
-
-    //         Overlay::new(child)
-    //             .with_fit_mode(OverlayFitMode::SwitchAnchor)
-    //             .with_anchor_corner(AnchorCorner::TopLeft)
-    //             .with_z_index(999)
-    //             .aligned()
-    //             .bottom()
-    //             .left()
-    //             .into_any()
-    //     })
-    // }
-
-    // pub fn toggle_vcs_menu(&mut self, _: &ToggleVcsMenu, cx: &mut ViewContext<Self>) {
-    //     if self.branch_popover.take().is_none() {
-    //         if let Some(workspace) = self.workspace.upgrade(cx) {
-    //             let Some(view) =
-    //                 cx.add_option_view(|cx| build_branch_list(workspace, cx).log_err())
-    //             else {
-    //                 return;
-    //             };
-    //             cx.subscribe(&view, |this, _, event, cx| {
-    //                 match event {
-    //                     PickerEvent::Dismiss => {
-    //                         this.branch_popover = None;
-    //                     }
-    //                 }
+    fn render_branches_popover_host<'a>(&'a self) -> Option<AnyElement> {
+        self.branch_popover.as_ref().map(|child| {
+            overlay()
+                .child(div().min_w_64().child(child.clone()))
+                .into_any()
+        })
+    }
 
-    //                 cx.notify();
-    //             })
-    //             .detach();
-    //             self.project_popover.take();
-    //             cx.focus(&view);
-    //             self.branch_popover = Some(view);
-    //         }
-    //     }
+    pub fn toggle_vcs_menu(&mut self, _: &ToggleVcsMenu, cx: &mut ViewContext<Self>) {
+        if self.branch_popover.take().is_none() {
+            if let Some(workspace) = self.workspace.upgrade() {
+                let Some(view) = build_branch_list(workspace, cx).log_err() else {
+                    return;
+                };
+                cx.subscribe(&view, |this, _, _, cx| {
+                    this.branch_popover = None;
+                    cx.notify();
+                })
+                .detach();
+                self.project_popover.take();
+                let focus_handle = view.focus_handle(cx);
+                cx.focus(&focus_handle);
+                self.branch_popover = Some(view);
+            }
+        }
 
-    //     cx.notify();
-    // }
+        cx.notify();
+    }
 
     pub fn toggle_project_menu(&mut self, _: &ToggleProjectMenu, cx: &mut ViewContext<Self>) {
         let workspace = self.workspace.clone();

crates/collab_ui2/src/collab_ui.rs 🔗

@@ -34,7 +34,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
     ChatPanelSettings::register(cx);
     NotificationPanelSettings::register(cx);
 
-    // vcs_menu::init(cx);
+    vcs_menu::init(cx);
     collab_titlebar_item::init(cx);
     collab_panel::init(cx);
     channel_view::init(cx);

crates/vcs_menu2/Cargo.toml 🔗

@@ -0,0 +1,17 @@
+[package]
+name = "vcs_menu2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+fuzzy = {package = "fuzzy2", path = "../fuzzy2"}
+fs = {package = "fs2", path = "../fs2"}
+gpui = {package = "gpui2", path = "../gpui2"}
+picker = {package = "picker2", path = "../picker2"}
+util = {path = "../util"}
+ui = {package = "ui2", path = "../ui2"}
+workspace = {package = "workspace2", path = "../workspace2"}
+
+anyhow.workspace = true

crates/vcs_menu2/src/lib.rs 🔗

@@ -0,0 +1,359 @@
+use anyhow::{anyhow, bail, Result};
+use fs::repository::Branch;
+use fuzzy::{StringMatch, StringMatchCandidate};
+use gpui::{
+    actions, rems, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
+    ParentElement, Render, SharedString, Styled, Task, View, ViewContext, VisualContext,
+    WindowContext,
+};
+use picker::{Picker, PickerDelegate};
+use std::sync::Arc;
+use ui::{v_stack, HighlightedLabel, ListItem, Selectable};
+use util::ResultExt;
+use workspace::{ModalView, Toast, Workspace};
+
+actions!(branches, [OpenRecent]);
+
+pub fn init(cx: &mut AppContext) {
+    // todo!() po
+    cx.observe_new_views(|workspace: &mut Workspace, _| {
+        workspace.register_action(|workspace, action, cx| {
+            ModalBranchList::toggle_modal(workspace, action, cx).log_err();
+        });
+    })
+    .detach();
+}
+pub type BranchList = Picker<BranchListDelegate>;
+
+pub struct ModalBranchList {
+    pub picker: View<Picker<BranchListDelegate>>,
+}
+
+impl ModalView for ModalBranchList {}
+impl EventEmitter<DismissEvent> for ModalBranchList {}
+
+impl FocusableView for ModalBranchList {
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.picker.focus_handle(cx)
+    }
+}
+
+impl Render for ModalBranchList {
+    type Element = Div;
+
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+        v_stack().w(rems(34.)).child(self.picker.clone())
+    }
+}
+
+pub fn build_branch_list(
+    workspace: View<Workspace>,
+    cx: &mut WindowContext<'_>,
+) -> Result<View<BranchList>> {
+    let delegate = workspace.update(cx, |workspace, cx| {
+        BranchListDelegate::new(workspace, cx.view().clone(), 29, cx)
+    })?;
+
+    Ok(cx.build_view(|cx| Picker::new(delegate, cx)))
+}
+
+impl ModalBranchList {
+    fn toggle_modal(
+        workspace: &mut Workspace,
+        _: &OpenRecent,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Result<()> {
+        // Modal branch picker has a longer trailoff than a popover one.
+        let delegate = BranchListDelegate::new(workspace, cx.view().clone(), 70, cx)?;
+        workspace.toggle_modal(cx, |cx| ModalBranchList {
+            picker: cx.build_view(|cx| Picker::new(delegate, cx)),
+        });
+
+        Ok(())
+    }
+}
+
+pub struct BranchListDelegate {
+    matches: Vec<StringMatch>,
+    all_branches: Vec<Branch>,
+    workspace: View<Workspace>,
+    selected_index: usize,
+    last_query: String,
+    /// Max length of branch name before we truncate it and add a trailing `...`.
+    branch_name_trailoff_after: usize,
+}
+
+impl BranchListDelegate {
+    fn new(
+        workspace: &Workspace,
+        handle: View<Workspace>,
+        branch_name_trailoff_after: usize,
+        cx: &AppContext,
+    ) -> Result<Self> {
+        let project = workspace.project().read(&cx);
+        let Some(worktree) = project.visible_worktrees(cx).next() else {
+            bail!("Cannot update branch list as there are no visible worktrees")
+        };
+
+        let mut cwd = worktree.read(cx).abs_path().to_path_buf();
+        cwd.push(".git");
+        let Some(repo) = project.fs().open_repo(&cwd) else {
+            bail!("Project does not have associated git repository.")
+        };
+        let all_branches = repo.lock().branches()?;
+        Ok(Self {
+            matches: vec![],
+            workspace: handle,
+            all_branches,
+            selected_index: 0,
+            last_query: Default::default(),
+            branch_name_trailoff_after,
+        })
+    }
+
+    fn display_error_toast(&self, message: String, cx: &mut ViewContext<BranchList>) {
+        const GIT_CHECKOUT_FAILURE_ID: usize = 2048;
+        self.workspace.update(cx, |model, ctx| {
+            model.show_toast(Toast::new(GIT_CHECKOUT_FAILURE_ID, message), ctx)
+        });
+    }
+}
+
+impl PickerDelegate for BranchListDelegate {
+    type ListItem = ListItem;
+    fn placeholder_text(&self) -> Arc<str> {
+        "Select branch...".into()
+    }
+
+    fn match_count(&self) -> usize {
+        self.matches.len()
+    }
+
+    fn selected_index(&self) -> usize {
+        self.selected_index
+    }
+
+    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
+        self.selected_index = ix;
+    }
+
+    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
+        cx.spawn(move |picker, mut cx| async move {
+            let candidates = picker.update(&mut cx, |view, _| {
+                const RECENT_BRANCHES_COUNT: usize = 10;
+                let mut branches = view.delegate.all_branches.clone();
+                if query.is_empty() && branches.len() > RECENT_BRANCHES_COUNT {
+                    // Truncate list of recent branches
+                    // Do a partial sort to show recent-ish branches first.
+                    branches.select_nth_unstable_by(RECENT_BRANCHES_COUNT - 1, |lhs, rhs| {
+                        rhs.unix_timestamp.cmp(&lhs.unix_timestamp)
+                    });
+                    branches.truncate(RECENT_BRANCHES_COUNT);
+                    branches.sort_unstable_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
+                }
+                branches
+                    .into_iter()
+                    .enumerate()
+                    .map(|(ix, command)| StringMatchCandidate {
+                        id: ix,
+                        char_bag: command.name.chars().collect(),
+                        string: command.name.into(),
+                    })
+                    .collect::<Vec<StringMatchCandidate>>()
+            });
+            let Some(candidates) = candidates.log_err() else {
+                return;
+            };
+            let matches = if query.is_empty() {
+                candidates
+                    .into_iter()
+                    .enumerate()
+                    .map(|(index, candidate)| StringMatch {
+                        candidate_id: index,
+                        string: candidate.string,
+                        positions: Vec::new(),
+                        score: 0.0,
+                    })
+                    .collect()
+            } else {
+                fuzzy::match_strings(
+                    &candidates,
+                    &query,
+                    true,
+                    10000,
+                    &Default::default(),
+                    cx.background_executor().clone(),
+                )
+                .await
+            };
+            picker
+                .update(&mut cx, |picker, _| {
+                    let delegate = &mut picker.delegate;
+                    delegate.matches = matches;
+                    if delegate.matches.is_empty() {
+                        delegate.selected_index = 0;
+                    } else {
+                        delegate.selected_index =
+                            core::cmp::min(delegate.selected_index, delegate.matches.len() - 1);
+                    }
+                    delegate.last_query = query;
+                })
+                .log_err();
+        })
+    }
+
+    fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
+        let current_pick = self.selected_index();
+        let Some(current_pick) = self
+            .matches
+            .get(current_pick)
+            .map(|pick| pick.string.clone())
+        else {
+            return;
+        };
+        cx.spawn(|picker, mut cx| async move {
+            picker
+                .update(&mut cx, |this, cx| {
+                    let project = this.delegate.workspace.read(cx).project().read(cx);
+                    let mut cwd = project
+                        .visible_worktrees(cx)
+                        .next()
+                        .ok_or_else(|| anyhow!("There are no visisible worktrees."))?
+                        .read(cx)
+                        .abs_path()
+                        .to_path_buf();
+                    cwd.push(".git");
+                    let status = project
+                        .fs()
+                        .open_repo(&cwd)
+                        .ok_or_else(|| {
+                            anyhow!(
+                                "Could not open repository at path `{}`",
+                                cwd.as_os_str().to_string_lossy()
+                            )
+                        })?
+                        .lock()
+                        .change_branch(&current_pick);
+                    if status.is_err() {
+                        this.delegate.display_error_toast(format!("Failed to checkout branch '{current_pick}', check for conflicts or unstashed files"), cx);
+                        status?;
+                    }
+                    cx.emit(DismissEvent);
+
+                    Ok::<(), anyhow::Error>(())
+                })
+                .log_err();
+        })
+        .detach();
+    }
+
+    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
+        cx.emit(DismissEvent);
+    }
+
+    fn render_match(
+        &self,
+        ix: usize,
+        selected: bool,
+        _cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<Self::ListItem> {
+        let hit = &self.matches[ix];
+        let shortened_branch_name =
+            util::truncate_and_trailoff(&hit.string, self.branch_name_trailoff_after);
+        let highlights: Vec<_> = hit
+            .positions
+            .iter()
+            .filter(|index| index < &&self.branch_name_trailoff_after)
+            .copied()
+            .collect();
+        Some(
+            ListItem::new(SharedString::from(format!("vcs-menu-{ix}")))
+                .start_slot(HighlightedLabel::new(shortened_branch_name, highlights))
+                .selected(selected),
+        )
+    }
+    // fn render_header(
+    //     &self,
+    //     cx: &mut ViewContext<Picker<Self>>,
+    // ) -> Option<AnyElement<Picker<Self>>> {
+    //     let theme = &theme::current(cx);
+    //     let style = theme.picker.header.clone();
+    //     let label = if self.last_query.is_empty() {
+    //         Flex::row()
+    //             .with_child(Label::new("Recent branches", style.label.clone()))
+    //             .contained()
+    //             .with_style(style.container)
+    //     } else {
+    //         Flex::row()
+    //             .with_child(Label::new("Branches", style.label.clone()))
+    //             .with_children(self.matches.is_empty().not().then(|| {
+    //                 let suffix = if self.matches.len() == 1 { "" } else { "es" };
+    //                 Label::new(
+    //                     format!("{} match{}", self.matches.len(), suffix),
+    //                     style.label,
+    //                 )
+    //                 .flex_float()
+    //             }))
+    //             .contained()
+    //             .with_style(style.container)
+    //     };
+    //     Some(label.into_any())
+    // }
+    // fn render_footer(
+    //     &self,
+    //     cx: &mut ViewContext<Picker<Self>>,
+    // ) -> Option<AnyElement<Picker<Self>>> {
+    //     if !self.last_query.is_empty() {
+    //         let theme = &theme::current(cx);
+    //         let style = theme.picker.footer.clone();
+    //         enum BranchCreateButton {}
+    //         Some(
+    //             Flex::row().with_child(MouseEventHandler::new::<BranchCreateButton, _>(0, cx, |state, _| {
+    //                 let style = style.style_for(state);
+    //                 Label::new("Create branch", style.label.clone())
+    //                     .contained()
+    //                     .with_style(style.container)
+    //             })
+    //             .with_cursor_style(CursorStyle::PointingHand)
+    //             .on_down(MouseButton::Left, |_, _, cx| {
+    //                 cx.spawn(|picker, mut cx| async move {
+    //                     picker.update(&mut cx, |this, cx| {
+    //                         let project = this.delegate().workspace.read(cx).project().read(cx);
+    //                         let current_pick = &this.delegate().last_query;
+    //                         let mut cwd = project
+    //                         .visible_worktrees(cx)
+    //                         .next()
+    //                         .ok_or_else(|| anyhow!("There are no visisible worktrees."))?
+    //                         .read(cx)
+    //                         .abs_path()
+    //                         .to_path_buf();
+    //                         cwd.push(".git");
+    //                         let repo = project
+    //                             .fs()
+    //                             .open_repo(&cwd)
+    //                             .ok_or_else(|| anyhow!("Could not open repository at path `{}`", cwd.as_os_str().to_string_lossy()))?;
+    //                         let repo = repo
+    //                             .lock();
+    //                         let status = repo
+    //                             .create_branch(&current_pick);
+    //                         if status.is_err() {
+    //                             this.delegate().display_error_toast(format!("Failed to create branch '{current_pick}', check for conflicts or unstashed files"), cx);
+    //                             status?;
+    //                         }
+    //                         let status = repo.change_branch(&current_pick);
+    //                         if status.is_err() {
+    //                             this.delegate().display_error_toast(format!("Failed to chec branch '{current_pick}', check for conflicts or unstashed files"), cx);
+    //                             status?;
+    //                         }
+    //                         cx.emit(PickerEvent::Dismiss);
+    //                         Ok::<(), anyhow::Error>(())
+    //             })
+    //                 }).detach();
+    //             })).aligned().right()
+    //             .into_any(),
+    //         )
+    //     } else {
+    //         None
+    //     }
+    // }
+}