@@ -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<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 {
fn placeholder_text(&self) -> Arc<str> {
"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<Picker<Self>>) {
@@ -235,4 +244,60 @@ impl PickerDelegate for BranchListDelegate {
};
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::<BranchCreateButton, _>::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
+ }
+ }
}
@@ -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<GitFileStatus> {