Start work on adding a filter editor to the picker

Implement picker as a view instead of as a component

Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
Max Brunsfeld 2023-11-07 13:37:10 -08:00
parent 80e6427eec
commit b9d051eae7
5 changed files with 173 additions and 197 deletions

2
Cargo.lock generated
View file

@ -8557,8 +8557,10 @@ dependencies = [
"backtrace-on-stack-overflow", "backtrace-on-stack-overflow",
"chrono", "chrono",
"clap 4.4.4", "clap 4.4.4",
"editor2",
"gpui2", "gpui2",
"itertools 0.11.0", "itertools 0.11.0",
"language2",
"log", "log",
"menu2", "menu2",
"picker2", "picker2",

View file

@ -1,149 +1,121 @@
use editor::Editor;
use gpui::{ use gpui::{
div, uniform_list, Component, ElementId, FocusHandle, ParentElement, StatelessInteractive, div, uniform_list, Component, Div, ParentElement, Render, StatelessInteractive, Styled,
Styled, UniformListScrollHandle, ViewContext, UniformListScrollHandle, View, ViewContext, VisualContext,
}; };
use std::cmp; use std::cmp;
#[derive(Component)] pub struct Picker<D: PickerDelegate> {
pub struct Picker<V: PickerDelegate> { pub delegate: D,
id: ElementId, scroll_handle: UniformListScrollHandle,
focus_handle: FocusHandle, editor: View<Editor>,
phantom: std::marker::PhantomData<V>,
} }
pub trait PickerDelegate: Sized + 'static { pub trait PickerDelegate: Sized + 'static {
type ListItem: Component<Self>; type ListItem: Component<Picker<Self>>;
fn match_count(&self, picker_id: ElementId) -> usize; fn match_count(&self) -> usize;
fn selected_index(&self, picker_id: ElementId) -> usize; fn selected_index(&self) -> usize;
fn set_selected_index(&mut self, ix: usize, picker_id: ElementId, cx: &mut ViewContext<Self>); fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
// fn placeholder_text(&self) -> Arc<str>; // fn placeholder_text(&self) -> Arc<str>;
// fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>; // fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
fn confirm(&mut self, secondary: bool, picker_id: ElementId, cx: &mut ViewContext<Self>); fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
fn dismissed(&mut self, picker_id: ElementId, cx: &mut ViewContext<Self>); fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
selected: bool, selected: bool,
picker_id: ElementId, cx: &mut ViewContext<Picker<Self>>,
cx: &mut ViewContext<Self>,
) -> Self::ListItem; ) -> Self::ListItem;
} }
impl<V: PickerDelegate> Picker<V> { impl<D: PickerDelegate> Picker<D> {
pub fn new(id: impl Into<ElementId>, focus_handle: FocusHandle) -> Self { pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
Self { Self {
id: id.into(), delegate,
focus_handle, scroll_handle: UniformListScrollHandle::new(),
phantom: std::marker::PhantomData, editor: cx.build_view(|cx| Editor::single_line(cx)),
} }
} }
fn bind_actions<T: StatelessInteractive<V>>( fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
div: T, let count = self.delegate.match_count();
id: ElementId,
scroll_handle: &UniformListScrollHandle,
) -> T {
div.on_action({
let id = id.clone();
let scroll_handle = scroll_handle.clone();
move |view: &mut V, _: &menu::SelectNext, cx| {
let count = view.match_count(id.clone());
if count > 0 { if count > 0 {
let index = view.selected_index(id.clone()); let index = self.delegate.selected_index();
let ix = cmp::min(index + 1, count - 1); let ix = cmp::min(index + 1, count - 1);
view.set_selected_index(ix, id.clone(), cx); self.delegate.set_selected_index(ix, cx);
scroll_handle.scroll_to_item(ix); self.scroll_handle.scroll_to_item(ix);
} }
} }
})
.on_action({ fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
let id = id.clone(); let count = self.delegate.match_count();
let scroll_handle = scroll_handle.clone();
move |view, _: &menu::SelectPrev, cx| {
let count = view.match_count(id.clone());
if count > 0 { if count > 0 {
let index = view.selected_index(id.clone()); let index = self.delegate.selected_index();
let ix = index.saturating_sub(1); let ix = index.saturating_sub(1);
view.set_selected_index(ix, id.clone(), cx); self.delegate.set_selected_index(ix, cx);
scroll_handle.scroll_to_item(ix); self.scroll_handle.scroll_to_item(ix);
} }
} }
})
.on_action({ fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
let id = id.clone(); let count = self.delegate.match_count();
let scroll_handle = scroll_handle.clone();
move |view: &mut V, _: &menu::SelectFirst, cx| {
let count = view.match_count(id.clone());
if count > 0 { if count > 0 {
view.set_selected_index(0, id.clone(), cx); self.delegate.set_selected_index(0, cx);
scroll_handle.scroll_to_item(0); self.scroll_handle.scroll_to_item(0);
} }
} }
})
.on_action({ fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
let id = id.clone(); let count = self.delegate.match_count();
let scroll_handle = scroll_handle.clone();
move |view: &mut V, _: &menu::SelectLast, cx| {
let count = view.match_count(id.clone());
if count > 0 { if count > 0 {
view.set_selected_index(count - 1, id.clone(), cx); self.delegate.set_selected_index(count - 1, cx);
scroll_handle.scroll_to_item(count - 1); self.scroll_handle.scroll_to_item(count - 1);
} }
} }
})
.on_action({ fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
let id = id.clone(); self.delegate.dismissed(cx);
move |view: &mut V, _: &menu::Cancel, cx| {
view.dismissed(id.clone(), cx);
} }
})
.on_action({ fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
let id = id.clone(); self.delegate.confirm(false, cx);
move |view: &mut V, _: &menu::Confirm, cx| {
view.confirm(false, id.clone(), cx);
} }
})
.on_action({ fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
let id = id.clone(); self.delegate.confirm(true, cx);
move |view: &mut V, _: &menu::SecondaryConfirm, cx| {
view.confirm(true, id.clone(), cx);
}
})
} }
} }
impl<V: 'static + PickerDelegate> Picker<V> { impl<D: PickerDelegate> Render for Picker<D> {
pub fn render(self, view: &mut V, _cx: &mut ViewContext<V>) -> impl Component<V> { type Element = Div<Self>;
let id = self.id.clone();
let scroll_handle = UniformListScrollHandle::new(); fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
Self::bind_actions(
div() div()
.id(self.id.clone())
.size_full() .size_full()
.track_focus(&self.focus_handle)
.context("picker") .context("picker")
.on_action(Self::select_next)
.on_action(Self::select_prev)
.on_action(Self::select_first)
.on_action(Self::select_last)
.on_action(Self::cancel)
.on_action(Self::confirm)
.on_action(Self::secondary_confirm)
.child(self.editor.clone())
.child( .child(
uniform_list( uniform_list("candidates", self.delegate.match_count(), {
"candidates", move |this: &mut Self, visible_range, cx| {
view.match_count(self.id.clone()), let selected_ix = this.delegate.selected_index();
move |view: &mut V, visible_range, cx| {
let selected_ix = view.selected_index(self.id.clone());
visible_range visible_range
.map(|ix| { .map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
view.render_match(ix, ix == selected_ix, self.id.clone(), cx)
})
.collect() .collect()
}, }
) })
.track_scroll(scroll_handle.clone()) .track_scroll(self.scroll_handle.clone())
.size_full(), .size_full(),
),
id,
&scroll_handle,
) )
} }
} }

View file

@ -13,9 +13,11 @@ anyhow.workspace = true
# TODO: Remove after diagnosing stack overflow. # TODO: Remove after diagnosing stack overflow.
backtrace-on-stack-overflow = "0.3.0" backtrace-on-stack-overflow = "0.3.0"
clap = { version = "4.4", features = ["derive", "string"] } clap = { version = "4.4", features = ["derive", "string"] }
editor = { package = "editor2", path = "../editor2" }
chrono = "0.4" chrono = "0.4"
gpui = { package = "gpui2", path = "../gpui2" } gpui = { package = "gpui2", path = "../gpui2" }
itertools = "0.11.0" itertools = "0.11.0"
language = { package = "language2", path = "../language2" }
log.workspace = true log.workspace = true
rust-embed.workspace = true rust-embed.workspace = true
serde.workspace = true serde.workspace = true

View file

@ -6,15 +6,19 @@ use picker::{Picker, PickerDelegate};
use theme2::ActiveTheme; use theme2::ActiveTheme;
pub struct PickerStory { pub struct PickerStory {
selected_ix: usize, picker: View<Picker<Delegate>>,
candidates: Vec<SharedString>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
} }
impl PickerDelegate for PickerStory { struct Delegate {
type ListItem = Div<Self>; candidates: Vec<SharedString>,
selected_ix: usize,
}
fn match_count(&self, _picker_id: gpui::ElementId) -> usize { impl PickerDelegate for Delegate {
type ListItem = Div<Picker<Self>>;
fn match_count(&self) -> usize {
self.candidates.len() self.candidates.len()
} }
@ -22,8 +26,7 @@ impl PickerDelegate for PickerStory {
&self, &self,
ix: usize, ix: usize,
selected: bool, selected: bool,
_picker_id: gpui::ElementId, cx: &mut gpui::ViewContext<Picker<Self>>,
cx: &mut gpui::ViewContext<Self>,
) -> Self::ListItem { ) -> Self::ListItem {
let colors = cx.theme().colors(); let colors = cx.theme().colors();
@ -40,26 +43,16 @@ impl PickerDelegate for PickerStory {
.child(self.candidates[ix].clone()) .child(self.candidates[ix].clone())
} }
fn selected_index(&self, picker_id: gpui::ElementId) -> usize { fn selected_index(&self) -> usize {
self.selected_ix self.selected_ix
} }
fn set_selected_index( fn set_selected_index(&mut self, ix: usize, cx: &mut gpui::ViewContext<Picker<Self>>) {
&mut self,
ix: usize,
_picker_id: gpui::ElementId,
cx: &mut gpui::ViewContext<Self>,
) {
self.selected_ix = ix; self.selected_ix = ix;
cx.notify(); cx.notify();
} }
fn confirm( fn confirm(&mut self, secondary: bool, cx: &mut gpui::ViewContext<Picker<Self>>) {
&mut self,
secondary: bool,
picker_id: gpui::ElementId,
cx: &mut gpui::ViewContext<Self>,
) {
if secondary { if secondary {
eprintln!("Secondary confirmed {}", self.candidates[self.selected_ix]) eprintln!("Secondary confirmed {}", self.candidates[self.selected_ix])
} else { } else {
@ -67,7 +60,7 @@ impl PickerDelegate for PickerStory {
} }
} }
fn dismissed(&mut self, picker_id: gpui::ElementId, cx: &mut gpui::ViewContext<Self>) { fn dismissed(&mut self, cx: &mut gpui::ViewContext<Picker<Self>>) {
cx.quit(); cx.quit();
} }
} }
@ -98,6 +91,9 @@ impl PickerStory {
PickerStory { PickerStory {
focus_handle, focus_handle,
picker: cx.build_view(|cx| {
Picker::new(
Delegate {
candidates: vec![ candidates: vec![
"Baguette (France)".into(), "Baguette (France)".into(),
"Baklava (Turkey)".into(), "Baklava (Turkey)".into(),
@ -150,6 +146,10 @@ impl PickerStory {
"Wiener Schnitzel (Austria)".into(), "Wiener Schnitzel (Austria)".into(),
], ],
selected_ix: 0, selected_ix: 0,
},
cx,
)
}),
} }
}) })
} }
@ -159,11 +159,9 @@ impl Render for PickerStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme();
div() div()
.bg(theme.styles.colors.background) .bg(cx.theme().styles.colors.background)
.size_full() .size_full()
.child(Picker::new("picker_story", self.focus_handle.clone())) .child(self.picker.clone())
} }
} }

View file

@ -72,6 +72,8 @@ fn main() {
ThemeSettings::override_global(theme_settings, cx); ThemeSettings::override_global(theme_settings, cx);
ui::settings::init(cx); ui::settings::init(cx);
language::init(cx);
editor::init(cx);
let window = cx.open_window( let window = cx.open_window(
WindowOptions { WindowOptions {