diff --git a/crates/collab_ui/src/branch_list.rs b/crates/collab_ui/src/branch_list.rs index 16fefbd2eb..e6f1504503 100644 --- a/crates/collab_ui/src/branch_list.rs +++ b/crates/collab_ui/src/branch_list.rs @@ -1,6 +1,8 @@ use anyhow::{anyhow, bail}; use fuzzy::{StringMatch, StringMatchCandidate}; -use gpui::{elements::*, AppContext, MouseState, Task, ViewContext, ViewHandle}; +use gpui::{ + elements::*, platform::MouseButton, AppContext, MouseState, Task, ViewContext, ViewHandle, +}; use picker::{Picker, PickerDelegate, PickerEvent}; use std::{ops::Not, sync::Arc}; use util::ResultExt; @@ -35,6 +37,14 @@ pub struct BranchListDelegate { last_query: String, } +impl BranchListDelegate { + fn display_error_toast(&self, message: String, cx: &mut ViewContext) { + 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 { fn placeholder_text(&self) -> Arc { "Select branch...".into() @@ -136,40 +146,39 @@ impl PickerDelegate for BranchListDelegate { let current_pick = self.selected_index(); let current_pick = self.matches[current_pick].string.clone(); 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(¤t_pick); - if status.is_err() { - const GIT_CHECKOUT_FAILURE_ID: usize = 2048; - this.delegate().workspace.update(cx, |model, ctx| { - model.show_toast( - Toast::new( - GIT_CHECKOUT_FAILURE_ID, - format!("Failed to checkout branch '{current_pick}', check for conflicts or unstashed files"), - ), - ctx, - ) - }); - status?; - } - cx.emit(PickerEvent::Dismiss); + 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(¤t_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(PickerEvent::Dismiss); - Ok::<(), anyhow::Error>(()) - }).log_err(); - }).detach(); + Ok::<(), anyhow::Error>(()) + }) + .log_err(); + }) + .detach(); } fn dismissed(&mut self, cx: &mut ViewContext>) { @@ -235,4 +244,60 @@ impl PickerDelegate for BranchListDelegate { }; Some(label.into_any()) } + fn render_footer( + &self, + cx: &mut ViewContext>, + ) -> Option>> { + 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(0, cx, |_, _| { + Label::new("Create branch", style.label.clone()) + .contained() + .with_style(style.container) + .aligned() + .right() + }) + .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(¤t_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(¤t_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?; + } + Ok::<(), anyhow::Error>(()) + }) + }).detach(); + })) + .into_any(), + ) + } else { + None + } + } } diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 0e5fd8343f..ed9aa85a89 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -39,6 +39,9 @@ pub trait GitRepository: Send { fn change_branch(&self, _: &str) -> Result<()> { Ok(()) } + fn create_branch(&self, _: &str) -> Result<()> { + Ok(()) + } } impl std::fmt::Debug for dyn GitRepository { @@ -152,6 +155,12 @@ impl GitRepository for LibGitRepository { )?; Ok(()) } + fn create_branch(&self, name: &str) -> Result<()> { + let current_commit = self.head()?.peel_to_commit()?; + self.branch(name, ¤t_commit, false)?; + + Ok(()) + } } fn read_status(status: git2::Status) -> Option {