From 25a5eda76f2b66873d80403a97e067f2ad70a24b Mon Sep 17 00:00:00 2001
From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Date: Fri, 22 Dec 2023 18:10:59 +0100
Subject: [PATCH] picker: Reintroduce headers and footers (#3786)
Update VCS menu to match Zed1.
Release Notes:
- N/A
---
crates/picker2/src/picker2.rs | 15 +++-
crates/vcs_menu2/src/lib.rs | 163 +++++++++++++++-------------------
2 files changed, 86 insertions(+), 92 deletions(-)
diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs
index aa88d70dc4..8585d9e73b 100644
--- a/crates/picker2/src/picker2.rs
+++ b/crates/picker2/src/picker2.rs
@@ -1,8 +1,8 @@
use editor::Editor;
use gpui::{
- div, prelude::*, uniform_list, AppContext, DismissEvent, Div, EventEmitter, FocusHandle,
- FocusableView, Length, MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle,
- View, ViewContext, WindowContext,
+ div, prelude::*, uniform_list, AnyElement, AppContext, DismissEvent, Div, EventEmitter,
+ FocusHandle, FocusableView, Length, MouseButton, MouseDownEvent, Render, Task,
+ UniformListScrollHandle, View, ViewContext, WindowContext,
};
use std::{cmp, sync::Arc};
use ui::{prelude::*, v_stack, Color, Divider, Label, ListItem, ListItemSpacing};
@@ -40,6 +40,12 @@ pub trait PickerDelegate: Sized + 'static {
selected: bool,
cx: &mut ViewContext>,
) -> Option;
+ fn render_header(&self, _: &mut ViewContext>) -> Option {
+ None
+ }
+ fn render_footer(&self, _: &mut ViewContext>) -> Option {
+ None
+ }
}
impl FocusableView for Picker {
@@ -253,6 +259,7 @@ impl Render for Picker {
v_stack()
.flex_grow()
.py_2()
+ .children(self.delegate.render_header(cx))
.child(
uniform_list(
cx.view().clone(),
@@ -286,6 +293,7 @@ impl Render for Picker {
)
.track_scroll(self.scroll_handle.clone())
)
+
.max_h_72()
.overflow_hidden(),
)
@@ -301,5 +309,6 @@ impl Render for Picker {
),
)
})
+ .children(self.delegate.render_footer(cx))
}
}
diff --git a/crates/vcs_menu2/src/lib.rs b/crates/vcs_menu2/src/lib.rs
index cece283d4e..497ea1a012 100644
--- a/crates/vcs_menu2/src/lib.rs
+++ b/crates/vcs_menu2/src/lib.rs
@@ -2,13 +2,16 @@ use anyhow::{anyhow, bail, Result};
use fs::repository::Branch;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
- actions, rems, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
- InteractiveElement, ParentElement, Render, SharedString, Styled, Subscription, Task, View,
- ViewContext, VisualContext, WindowContext,
+ actions, rems, AnyElement, AppContext, DismissEvent, Div, Element, EventEmitter, FocusHandle,
+ FocusableView, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled,
+ Subscription, Task, View, ViewContext, VisualContext, WindowContext,
};
use picker::{Picker, PickerDelegate};
-use std::sync::Arc;
-use ui::{v_stack, HighlightedLabel, ListItem, ListItemSpacing, Selectable};
+use std::{ops::Not, sync::Arc};
+use ui::{
+ h_stack, v_stack, Button, ButtonCommon, Clickable, HighlightedLabel, Label, LabelCommon,
+ LabelSize, ListItem, ListItemSpacing, Selectable,
+};
use util::ResultExt;
use workspace::{ModalView, Toast, Workspace};
@@ -288,88 +291,70 @@ impl PickerDelegate for BranchListDelegate {
.start_slot(HighlightedLabel::new(shortened_branch_name, highlights)),
)
}
- // fn render_header(
- // &self,
- // cx: &mut ViewContext>,
- // ) -> Option>> {
- // 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>,
- // ) -> 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, |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(¤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?;
- // }
- // cx.emit(PickerEvent::Dismiss);
- // Ok::<(), anyhow::Error>(())
- // })
- // }).detach();
- // })).aligned().right()
- // .into_any(),
- // )
- // } else {
- // None
- // }
- // }
+ fn render_header(&self, _: &mut ViewContext>) -> Option {
+ let label = if self.last_query.is_empty() {
+ h_stack()
+ .ml_3()
+ .child(Label::new("Recent branches").size(LabelSize::Small))
+ } else {
+ let match_label = self.matches.is_empty().not().then(|| {
+ let suffix = if self.matches.len() == 1 { "" } else { "es" };
+ Label::new(format!("{} match{}", self.matches.len(), suffix)).size(LabelSize::Small)
+ });
+ h_stack()
+ .px_3()
+ .h_full()
+ .justify_between()
+ .child(Label::new("Branches").size(LabelSize::Small))
+ .children(match_label)
+ };
+ Some(label.into_any())
+ }
+ fn render_footer(&self, cx: &mut ViewContext>) -> Option {
+ if self.last_query.is_empty() {
+ return None;
+ }
+
+ Some(
+ h_stack().mr_3().pb_2().child(h_stack().w_full()).child(
+ Button::new("branch-picker-create-branch-button", "Create branch").on_click(
+ cx.listener(|_, _, 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?;
+ }
+ this.cancel(&Default::default(), cx);
+ Ok::<(), anyhow::Error>(())
+ })
+
+ }).detach_and_log_err(cx);
+ }),
+ ).style(ui::ButtonStyle::Filled)).into_any_element(),
+ )
+ }
}