picker: Reintroduce headers and footers (#3786)

Update VCS menu to match Zed1.
<img width="444" alt="image"
src="https://github.com/zed-industries/zed/assets/24362066/6cb27510-f501-46bc-862f-1fb78006b77c">

Release Notes:

- N/A
This commit is contained in:
Piotr Osiewicz 2023-12-22 18:10:59 +01:00 committed by GitHub
parent 87ff5f04cb
commit 25a5eda76f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 86 additions and 92 deletions

View file

@ -1,8 +1,8 @@
use editor::Editor; use editor::Editor;
use gpui::{ use gpui::{
div, prelude::*, uniform_list, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, div, prelude::*, uniform_list, AnyElement, AppContext, DismissEvent, Div, EventEmitter,
FocusableView, Length, MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle, FocusHandle, FocusableView, Length, MouseButton, MouseDownEvent, Render, Task,
View, ViewContext, WindowContext, UniformListScrollHandle, View, ViewContext, WindowContext,
}; };
use std::{cmp, sync::Arc}; use std::{cmp, sync::Arc};
use ui::{prelude::*, v_stack, Color, Divider, Label, ListItem, ListItemSpacing}; use ui::{prelude::*, v_stack, Color, Divider, Label, ListItem, ListItemSpacing};
@ -40,6 +40,12 @@ pub trait PickerDelegate: Sized + 'static {
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem>; ) -> Option<Self::ListItem>;
fn render_header(&self, _: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
None
}
fn render_footer(&self, _: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
None
}
} }
impl<D: PickerDelegate> FocusableView for Picker<D> { impl<D: PickerDelegate> FocusableView for Picker<D> {
@ -253,6 +259,7 @@ impl<D: PickerDelegate> Render for Picker<D> {
v_stack() v_stack()
.flex_grow() .flex_grow()
.py_2() .py_2()
.children(self.delegate.render_header(cx))
.child( .child(
uniform_list( uniform_list(
cx.view().clone(), cx.view().clone(),
@ -286,6 +293,7 @@ impl<D: PickerDelegate> Render for Picker<D> {
) )
.track_scroll(self.scroll_handle.clone()) .track_scroll(self.scroll_handle.clone())
) )
.max_h_72() .max_h_72()
.overflow_hidden(), .overflow_hidden(),
) )
@ -301,5 +309,6 @@ impl<D: PickerDelegate> Render for Picker<D> {
), ),
) )
}) })
.children(self.delegate.render_footer(cx))
} }
} }

View file

@ -2,13 +2,16 @@ use anyhow::{anyhow, bail, Result};
use fs::repository::Branch; use fs::repository::Branch;
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, rems, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, actions, rems, AnyElement, AppContext, DismissEvent, Div, Element, EventEmitter, FocusHandle,
InteractiveElement, ParentElement, Render, SharedString, Styled, Subscription, Task, View, FocusableView, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled,
ViewContext, VisualContext, WindowContext, Subscription, Task, View, ViewContext, VisualContext, WindowContext,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use std::sync::Arc; use std::{ops::Not, sync::Arc};
use ui::{v_stack, HighlightedLabel, ListItem, ListItemSpacing, Selectable}; use ui::{
h_stack, v_stack, Button, ButtonCommon, Clickable, HighlightedLabel, Label, LabelCommon,
LabelSize, ListItem, ListItemSpacing, Selectable,
};
use util::ResultExt; use util::ResultExt;
use workspace::{ModalView, Toast, Workspace}; use workspace::{ModalView, Toast, Workspace};
@ -288,88 +291,70 @@ impl PickerDelegate for BranchListDelegate {
.start_slot(HighlightedLabel::new(shortened_branch_name, highlights)), .start_slot(HighlightedLabel::new(shortened_branch_name, highlights)),
) )
} }
// fn render_header( fn render_header(&self, _: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
// &self, let label = if self.last_query.is_empty() {
// cx: &mut ViewContext<Picker<Self>>, h_stack()
// ) -> Option<AnyElement<Picker<Self>>> { .ml_3()
// let theme = &theme::current(cx); .child(Label::new("Recent branches").size(LabelSize::Small))
// let style = theme.picker.header.clone(); } else {
// let label = if self.last_query.is_empty() { let match_label = self.matches.is_empty().not().then(|| {
// Flex::row() let suffix = if self.matches.len() == 1 { "" } else { "es" };
// .with_child(Label::new("Recent branches", style.label.clone())) Label::new(format!("{} match{}", self.matches.len(), suffix)).size(LabelSize::Small)
// .contained() });
// .with_style(style.container) h_stack()
// } else { .px_3()
// Flex::row() .h_full()
// .with_child(Label::new("Branches", style.label.clone())) .justify_between()
// .with_children(self.matches.is_empty().not().then(|| { .child(Label::new("Branches").size(LabelSize::Small))
// let suffix = if self.matches.len() == 1 { "" } else { "es" }; .children(match_label)
// Label::new( };
// format!("{} match{}", self.matches.len(), suffix), Some(label.into_any())
// style.label, }
// ) fn render_footer(&self, cx: &mut ViewContext<Picker<Self>>) -> Option<AnyElement> {
// .flex_float() if self.last_query.is_empty() {
// })) return None;
// .contained() }
// .with_style(style.container)
// }; Some(
// Some(label.into_any()) h_stack().mr_3().pb_2().child(h_stack().w_full()).child(
// } Button::new("branch-picker-create-branch-button", "Create branch").on_click(
// fn render_footer( cx.listener(|_, _, cx| {
// &self, cx.spawn(|picker, mut cx| async move {
// cx: &mut ViewContext<Picker<Self>>, picker.update(&mut cx, |this, cx| {
// ) -> Option<AnyElement<Picker<Self>>> { let project = this.delegate.workspace.read(cx).project().read(cx);
// if !self.last_query.is_empty() { let current_pick = &this.delegate.last_query;
// let theme = &theme::current(cx); let mut cwd = project
// let style = theme.picker.footer.clone(); .visible_worktrees(cx)
// enum BranchCreateButton {} .next()
// Some( .ok_or_else(|| anyhow!("There are no visisible worktrees."))?
// Flex::row().with_child(MouseEventHandler::new::<BranchCreateButton, _>(0, cx, |state, _| { .read(cx)
// let style = style.style_for(state); .abs_path()
// Label::new("Create branch", style.label.clone()) .to_path_buf();
// .contained() cwd.push(".git");
// .with_style(style.container) let repo = project
// }) .fs()
// .with_cursor_style(CursorStyle::PointingHand) .open_repo(&cwd)
// .on_down(MouseButton::Left, |_, _, cx| { .ok_or_else(|| anyhow!("Could not open repository at path `{}`", cwd.as_os_str().to_string_lossy()))?;
// cx.spawn(|picker, mut cx| async move { let repo = repo
// picker.update(&mut cx, |this, cx| { .lock();
// let project = this.delegate().workspace.read(cx).project().read(cx); let status = repo
// let current_pick = &this.delegate().last_query; .create_branch(&current_pick);
// let mut cwd = project if status.is_err() {
// .visible_worktrees(cx) this.delegate.display_error_toast(format!("Failed to create branch '{current_pick}', check for conflicts or unstashed files"), cx);
// .next() status?;
// .ok_or_else(|| anyhow!("There are no visisible worktrees."))? }
// .read(cx) let status = repo.change_branch(&current_pick);
// .abs_path() if status.is_err() {
// .to_path_buf(); this.delegate.display_error_toast(format!("Failed to chec branch '{current_pick}', check for conflicts or unstashed files"), cx);
// cwd.push(".git"); status?;
// let repo = project }
// .fs() this.cancel(&Default::default(), cx);
// .open_repo(&cwd) Ok::<(), anyhow::Error>(())
// .ok_or_else(|| anyhow!("Could not open repository at path `{}`", cwd.as_os_str().to_string_lossy()))?; })
// let repo = repo
// .lock(); }).detach_and_log_err(cx);
// let status = repo }),
// .create_branch(&current_pick); ).style(ui::ButtonStyle::Filled)).into_any_element(),
// 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
// }
// }
} }