From cb24cb1ea5d5cf36a61eb2450ee12f4ca8a685d6 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 7 Jul 2023 18:36:55 +0200 Subject: [PATCH 1/4] vcs: Add 'create branch' button --- crates/collab_ui/src/branch_list.rs | 133 +++++++++++++++++++++------- crates/fs/src/repository.rs | 9 ++ 2 files changed, 108 insertions(+), 34 deletions(-) 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 { From 4a69c711671b0fe826358b994199288cfbc0d860 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 7 Jul 2023 18:37:53 +0200 Subject: [PATCH 2/4] fixup! vcs: Add 'create branch' button --- crates/collab_ui/src/branch_list.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/collab_ui/src/branch_list.rs b/crates/collab_ui/src/branch_list.rs index e6f1504503..8772f88958 100644 --- a/crates/collab_ui/src/branch_list.rs +++ b/crates/collab_ui/src/branch_list.rs @@ -290,6 +290,7 @@ impl PickerDelegate for BranchListDelegate { 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(); From 92a0a4e3678b5d3bb2840bc3ba854cdaa16a141b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 10 Jul 2023 16:51:18 +0200 Subject: [PATCH 3/4] Add styles for branch create button --- crates/collab_ui/src/branch_list.rs | 7 +++-- crates/theme/src/theme.rs | 2 +- styles/src/style_tree/picker.ts | 42 +++++++++++++++++++++++------ 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/crates/collab_ui/src/branch_list.rs b/crates/collab_ui/src/branch_list.rs index 8772f88958..9b06e54d4e 100644 --- a/crates/collab_ui/src/branch_list.rs +++ b/crates/collab_ui/src/branch_list.rs @@ -253,12 +253,11 @@ impl PickerDelegate for BranchListDelegate { let style = theme.picker.footer.clone(); enum BranchCreateButton {} Some( - Flex::row().with_child(MouseEventHandler::::new(0, cx, |_, _| { + Flex::row().with_child(MouseEventHandler::::new(0, cx, |state, _| { + let style = style.style_for(state); 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 { @@ -294,7 +293,7 @@ impl PickerDelegate for BranchListDelegate { Ok::<(), anyhow::Error>(()) }) }).detach(); - })) + })).aligned().right() .into_any(), ) } else { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 1949a5d9bb..a47d97e002 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -586,7 +586,7 @@ pub struct Picker { pub no_matches: ContainedLabel, pub item: Toggleable>, pub header: ContainedLabel, - pub footer: ContainedLabel, + pub footer: Interactive, } #[derive(Clone, Debug, Deserialize, Default, JsonSchema)] diff --git a/styles/src/style_tree/picker.ts b/styles/src/style_tree/picker.ts index bbd664397f..b8817a25e9 100644 --- a/styles/src/style_tree/picker.ts +++ b/styles/src/style_tree/picker.ts @@ -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", "variant", { 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 + ), + }, + } + }), } } From 001e8483936c8afac8b88135236652f191887fdd Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 12 Jul 2023 12:40:37 -0400 Subject: [PATCH 4/4] Update picker footer button style Co-Authored-By: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> --- crates/vcs_menu/src/lib.rs | 7 +++++-- styles/src/style_tree/picker.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index 180eb463bd..184c1f4733 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/crates/vcs_menu/src/lib.rs @@ -1,8 +1,10 @@ use anyhow::{anyhow, bail, Result}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - actions, elements::*, platform::MouseButton, AppContext, MouseState, Task, ViewContext, - ViewHandle, + actions, + elements::*, + platform::{CursorStyle, MouseButton}, + AppContext, MouseState, Task, ViewContext, ViewHandle, }; use picker::{Picker, PickerDelegate, PickerEvent}; use std::{ops::Not, sync::Arc}; @@ -290,6 +292,7 @@ impl PickerDelegate for BranchListDelegate { .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| { diff --git a/styles/src/style_tree/picker.ts b/styles/src/style_tree/picker.ts index b8817a25e9..28ae854787 100644 --- a/styles/src/style_tree/picker.ts +++ b/styles/src/style_tree/picker.ts @@ -121,7 +121,7 @@ export default function picker(): any { }, footer: interactive({ base: { - text: text(theme.lowest, "sans", "variant", { size: "xs" }), + text: text(theme.lowest, "sans", "base", { size: "xs" }), padding: { bottom: 4, left: 12,