Piotr/z 2556 add create branch button (#2696)

Release Notes:

- N/A
This commit is contained in:
Piotr Osiewicz 2023-07-12 18:46:33 +02:00 committed by GitHub
commit e69240cf13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 147 additions and 43 deletions

View File

@ -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, &current_commit, false)?;
Ok(())
}
}
fn read_status(status: git2::Status) -> Option<GitFileStatus> {

View File

@ -586,7 +586,7 @@ pub struct Picker {
pub no_matches: ContainedLabel,
pub item: Toggleable<Interactive<ContainedLabel>>,
pub header: ContainedLabel,
pub footer: ContainedLabel,
pub footer: Interactive<ContainedLabel>,
}
#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]

View File

@ -1,6 +1,11 @@
use anyhow::{anyhow, bail, Result};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{actions, elements::*, AppContext, MouseState, Task, ViewContext, ViewHandle};
use gpui::{
actions,
elements::*,
platform::{CursorStyle, MouseButton},
AppContext, MouseState, Task, ViewContext, ViewHandle,
};
use picker::{Picker, PickerDelegate, PickerEvent};
use std::{ops::Not, sync::Arc};
use util::ResultExt;
@ -70,6 +75,14 @@ pub struct BranchListDelegate {
branch_name_trailoff_after: usize,
}
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()
@ -171,40 +184,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(&current_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(&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(PickerEvent::Dismiss);
Ok::<(), anyhow::Error>(())
}).log_err();
}).detach();
Ok::<(), anyhow::Error>(())
})
.log_err();
})
.detach();
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
@ -270,4 +282,61 @@ 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, |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
}
}
}

View File

@ -119,14 +119,40 @@ export default function picker(): any {
right: 8,
},
},
footer: {
text: text(theme.lowest, "sans", "variant", { size: "xs" }),
margin: {
top: 1,
left: 8,
right: 8,
footer: interactive({
base: {
text: text(theme.lowest, "sans", "base", { size: "xs" }),
padding: {
bottom: 4,
left: 12,
right: 12,
top: 4,
},
margin: {
top: 1,
left: 4,
right: 4,
},
corner_radius: 8,
background: with_opacity(
background(theme.lowest, "active"),
0.5
),
},
}
state: {
hovered: {
background: with_opacity(
background(theme.lowest, "hovered"),
0.5
),
},
clicked: {
background: with_opacity(
background(theme.lowest, "pressed"),
0.5
),
},
}
}),
}
}