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. image 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(), + ) + } }