Cargo.lock 🔗
@@ -8557,8 +8557,10 @@ dependencies = [
"backtrace-on-stack-overflow",
"chrono",
"clap 4.4.4",
+ "editor2",
"gpui2",
"itertools 0.11.0",
+ "language2",
"log",
"menu2",
"picker2",
Max Brunsfeld , Mikayla , and Marshall created
Implement picker as a view instead of as a component
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Marshall <marshall@zed.dev>
Cargo.lock | 2
crates/picker2/src/picker2.rs | 212 +++++++++++---------------
crates/storybook2/Cargo.toml | 2
crates/storybook2/src/stories/picker.rs | 152 +++++++++---------
crates/storybook2/src/storybook2.rs | 2
5 files changed, 173 insertions(+), 197 deletions(-)
@@ -8557,8 +8557,10 @@ dependencies = [
"backtrace-on-stack-overflow",
"chrono",
"clap 4.4.4",
+ "editor2",
"gpui2",
"itertools 0.11.0",
+ "language2",
"log",
"menu2",
"picker2",
@@ -1,149 +1,121 @@
+use editor::Editor;
use gpui::{
- div, uniform_list, Component, ElementId, FocusHandle, ParentElement, StatelessInteractive,
- Styled, UniformListScrollHandle, ViewContext,
+ div, uniform_list, Component, Div, ParentElement, Render, StatelessInteractive, Styled,
+ UniformListScrollHandle, View, ViewContext, VisualContext,
};
use std::cmp;
-#[derive(Component)]
-pub struct Picker<V: PickerDelegate> {
- id: ElementId,
- focus_handle: FocusHandle,
- phantom: std::marker::PhantomData<V>,
+pub struct Picker<D: PickerDelegate> {
+ pub delegate: D,
+ scroll_handle: UniformListScrollHandle,
+ editor: View<Editor>,
}
pub trait PickerDelegate: Sized + 'static {
- type ListItem: Component<Self>;
+ type ListItem: Component<Picker<Self>>;
- fn match_count(&self, picker_id: ElementId) -> usize;
- fn selected_index(&self, picker_id: ElementId) -> usize;
- fn set_selected_index(&mut self, ix: usize, picker_id: ElementId, cx: &mut ViewContext<Self>);
+ fn match_count(&self) -> usize;
+ fn selected_index(&self) -> usize;
+ fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
// fn placeholder_text(&self) -> Arc<str>;
// 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 dismissed(&mut self, picker_id: ElementId, cx: &mut ViewContext<Self>);
+ fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
+ fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
fn render_match(
&self,
ix: usize,
selected: bool,
- picker_id: ElementId,
- cx: &mut ViewContext<Self>,
+ cx: &mut ViewContext<Picker<Self>>,
) -> Self::ListItem;
}
-impl<V: PickerDelegate> Picker<V> {
- pub fn new(id: impl Into<ElementId>, focus_handle: FocusHandle) -> Self {
+impl<D: PickerDelegate> Picker<D> {
+ pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
Self {
- id: id.into(),
- focus_handle,
- phantom: std::marker::PhantomData,
+ delegate,
+ scroll_handle: UniformListScrollHandle::new(),
+ editor: cx.build_view(|cx| Editor::single_line(cx)),
}
}
- fn bind_actions<T: StatelessInteractive<V>>(
- div: T,
- 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 {
- let index = view.selected_index(id.clone());
- let ix = cmp::min(index + 1, count - 1);
- view.set_selected_index(ix, id.clone(), cx);
- scroll_handle.scroll_to_item(ix);
- }
- }
- })
- .on_action({
- let id = id.clone();
- let scroll_handle = scroll_handle.clone();
- move |view, _: &menu::SelectPrev, cx| {
- let count = view.match_count(id.clone());
- if count > 0 {
- let index = view.selected_index(id.clone());
- let ix = index.saturating_sub(1);
- view.set_selected_index(ix, id.clone(), cx);
- scroll_handle.scroll_to_item(ix);
- }
- }
- })
- .on_action({
- let id = id.clone();
- let scroll_handle = scroll_handle.clone();
- move |view: &mut V, _: &menu::SelectFirst, cx| {
- let count = view.match_count(id.clone());
- if count > 0 {
- view.set_selected_index(0, id.clone(), cx);
- scroll_handle.scroll_to_item(0);
- }
- }
- })
- .on_action({
- let id = id.clone();
- let scroll_handle = scroll_handle.clone();
- move |view: &mut V, _: &menu::SelectLast, cx| {
- let count = view.match_count(id.clone());
- if count > 0 {
- view.set_selected_index(count - 1, id.clone(), cx);
- scroll_handle.scroll_to_item(count - 1);
- }
- }
- })
- .on_action({
- let id = id.clone();
- move |view: &mut V, _: &menu::Cancel, cx| {
- view.dismissed(id.clone(), cx);
- }
- })
- .on_action({
- let id = id.clone();
- move |view: &mut V, _: &menu::Confirm, cx| {
- view.confirm(false, id.clone(), cx);
- }
- })
- .on_action({
- let id = id.clone();
- move |view: &mut V, _: &menu::SecondaryConfirm, cx| {
- view.confirm(true, id.clone(), cx);
- }
- })
+ fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
+ let count = self.delegate.match_count();
+ if count > 0 {
+ let index = self.delegate.selected_index();
+ let ix = cmp::min(index + 1, count - 1);
+ self.delegate.set_selected_index(ix, cx);
+ self.scroll_handle.scroll_to_item(ix);
+ }
+ }
+
+ fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
+ let count = self.delegate.match_count();
+ if count > 0 {
+ let index = self.delegate.selected_index();
+ let ix = index.saturating_sub(1);
+ self.delegate.set_selected_index(ix, cx);
+ self.scroll_handle.scroll_to_item(ix);
+ }
+ }
+
+ fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
+ let count = self.delegate.match_count();
+ if count > 0 {
+ self.delegate.set_selected_index(0, cx);
+ self.scroll_handle.scroll_to_item(0);
+ }
+ }
+
+ fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
+ let count = self.delegate.match_count();
+ if count > 0 {
+ self.delegate.set_selected_index(count - 1, cx);
+ self.scroll_handle.scroll_to_item(count - 1);
+ }
+ }
+
+ fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
+ self.delegate.dismissed(cx);
+ }
+
+ fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
+ self.delegate.confirm(false, cx);
+ }
+
+ fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
+ self.delegate.confirm(true, cx);
}
}
-impl<V: 'static + PickerDelegate> Picker<V> {
- pub fn render(self, view: &mut V, _cx: &mut ViewContext<V>) -> impl Component<V> {
- let id = self.id.clone();
- let scroll_handle = UniformListScrollHandle::new();
- Self::bind_actions(
- div()
- .id(self.id.clone())
- .size_full()
- .track_focus(&self.focus_handle)
- .context("picker")
- .child(
- uniform_list(
- "candidates",
- view.match_count(self.id.clone()),
- move |view: &mut V, visible_range, cx| {
- let selected_ix = view.selected_index(self.id.clone());
- visible_range
- .map(|ix| {
- view.render_match(ix, ix == selected_ix, self.id.clone(), cx)
- })
- .collect()
- },
- )
- .track_scroll(scroll_handle.clone())
- .size_full(),
- ),
- id,
- &scroll_handle,
- )
+impl<D: PickerDelegate> Render for Picker<D> {
+ type Element = Div<Self>;
+
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+ div()
+ .size_full()
+ .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(
+ uniform_list("candidates", self.delegate.match_count(), {
+ move |this: &mut Self, visible_range, cx| {
+ let selected_ix = this.delegate.selected_index();
+ visible_range
+ .map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
+ .collect()
+ }
+ })
+ .track_scroll(self.scroll_handle.clone())
+ .size_full(),
+ )
}
}
@@ -13,9 +13,11 @@ anyhow.workspace = true
# TODO: Remove after diagnosing stack overflow.
backtrace-on-stack-overflow = "0.3.0"
clap = { version = "4.4", features = ["derive", "string"] }
+editor = { package = "editor2", path = "../editor2" }
chrono = "0.4"
gpui = { package = "gpui2", path = "../gpui2" }
itertools = "0.11.0"
+language = { package = "language2", path = "../language2" }
log.workspace = true
rust-embed.workspace = true
serde.workspace = true
@@ -6,15 +6,19 @@ use picker::{Picker, PickerDelegate};
use theme2::ActiveTheme;
pub struct PickerStory {
- selected_ix: usize,
- candidates: Vec<SharedString>,
+ picker: View<Picker<Delegate>>,
focus_handle: FocusHandle,
}
-impl PickerDelegate for PickerStory {
- type ListItem = Div<Self>;
+struct Delegate {
+ candidates: Vec<SharedString>,
+ selected_ix: usize,
+}
+
+impl PickerDelegate for Delegate {
+ type ListItem = Div<Picker<Self>>;
- fn match_count(&self, _picker_id: gpui::ElementId) -> usize {
+ fn match_count(&self) -> usize {
self.candidates.len()
}
@@ -22,8 +26,7 @@ impl PickerDelegate for PickerStory {
&self,
ix: usize,
selected: bool,
- _picker_id: gpui::ElementId,
- cx: &mut gpui::ViewContext<Self>,
+ cx: &mut gpui::ViewContext<Picker<Self>>,
) -> Self::ListItem {
let colors = cx.theme().colors();
@@ -40,26 +43,16 @@ impl PickerDelegate for PickerStory {
.child(self.candidates[ix].clone())
}
- fn selected_index(&self, picker_id: gpui::ElementId) -> usize {
+ fn selected_index(&self) -> usize {
self.selected_ix
}
- fn set_selected_index(
- &mut self,
- ix: usize,
- _picker_id: gpui::ElementId,
- cx: &mut gpui::ViewContext<Self>,
- ) {
+ fn set_selected_index(&mut self, ix: usize, cx: &mut gpui::ViewContext<Picker<Self>>) {
self.selected_ix = ix;
cx.notify();
}
- fn confirm(
- &mut self,
- secondary: bool,
- picker_id: gpui::ElementId,
- cx: &mut gpui::ViewContext<Self>,
- ) {
+ fn confirm(&mut self, secondary: bool, cx: &mut gpui::ViewContext<Picker<Self>>) {
if secondary {
eprintln!("Secondary confirmed {}", self.candidates[self.selected_ix])
} 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();
}
}
@@ -98,58 +91,65 @@ impl PickerStory {
PickerStory {
focus_handle,
- candidates: vec![
- "Baguette (France)".into(),
- "Baklava (Turkey)".into(),
- "Beef Wellington (UK)".into(),
- "Biryani (India)".into(),
- "Borscht (Ukraine)".into(),
- "Bratwurst (Germany)".into(),
- "Bulgogi (Korea)".into(),
- "Burrito (USA)".into(),
- "Ceviche (Peru)".into(),
- "Chicken Tikka Masala (India)".into(),
- "Churrasco (Brazil)".into(),
- "Couscous (North Africa)".into(),
- "Croissant (France)".into(),
- "Dim Sum (China)".into(),
- "Empanada (Argentina)".into(),
- "Fajitas (Mexico)".into(),
- "Falafel (Middle East)".into(),
- "Feijoada (Brazil)".into(),
- "Fish and Chips (UK)".into(),
- "Fondue (Switzerland)".into(),
- "Goulash (Hungary)".into(),
- "Haggis (Scotland)".into(),
- "Kebab (Middle East)".into(),
- "Kimchi (Korea)".into(),
- "Lasagna (Italy)".into(),
- "Maple Syrup Pancakes (Canada)".into(),
- "Moussaka (Greece)".into(),
- "Pad Thai (Thailand)".into(),
- "Paella (Spain)".into(),
- "Pancakes (USA)".into(),
- "Pasta Carbonara (Italy)".into(),
- "Pavlova (Australia)".into(),
- "Peking Duck (China)".into(),
- "Pho (Vietnam)".into(),
- "Pierogi (Poland)".into(),
- "Pizza (Italy)".into(),
- "Poutine (Canada)".into(),
- "Pretzel (Germany)".into(),
- "Ramen (Japan)".into(),
- "Rendang (Indonesia)".into(),
- "Sashimi (Japan)".into(),
- "Satay (Indonesia)".into(),
- "Shepherd's Pie (Ireland)".into(),
- "Sushi (Japan)".into(),
- "Tacos (Mexico)".into(),
- "Tandoori Chicken (India)".into(),
- "Tortilla (Spain)".into(),
- "Tzatziki (Greece)".into(),
- "Wiener Schnitzel (Austria)".into(),
- ],
- selected_ix: 0,
+ picker: cx.build_view(|cx| {
+ Picker::new(
+ Delegate {
+ candidates: vec![
+ "Baguette (France)".into(),
+ "Baklava (Turkey)".into(),
+ "Beef Wellington (UK)".into(),
+ "Biryani (India)".into(),
+ "Borscht (Ukraine)".into(),
+ "Bratwurst (Germany)".into(),
+ "Bulgogi (Korea)".into(),
+ "Burrito (USA)".into(),
+ "Ceviche (Peru)".into(),
+ "Chicken Tikka Masala (India)".into(),
+ "Churrasco (Brazil)".into(),
+ "Couscous (North Africa)".into(),
+ "Croissant (France)".into(),
+ "Dim Sum (China)".into(),
+ "Empanada (Argentina)".into(),
+ "Fajitas (Mexico)".into(),
+ "Falafel (Middle East)".into(),
+ "Feijoada (Brazil)".into(),
+ "Fish and Chips (UK)".into(),
+ "Fondue (Switzerland)".into(),
+ "Goulash (Hungary)".into(),
+ "Haggis (Scotland)".into(),
+ "Kebab (Middle East)".into(),
+ "Kimchi (Korea)".into(),
+ "Lasagna (Italy)".into(),
+ "Maple Syrup Pancakes (Canada)".into(),
+ "Moussaka (Greece)".into(),
+ "Pad Thai (Thailand)".into(),
+ "Paella (Spain)".into(),
+ "Pancakes (USA)".into(),
+ "Pasta Carbonara (Italy)".into(),
+ "Pavlova (Australia)".into(),
+ "Peking Duck (China)".into(),
+ "Pho (Vietnam)".into(),
+ "Pierogi (Poland)".into(),
+ "Pizza (Italy)".into(),
+ "Poutine (Canada)".into(),
+ "Pretzel (Germany)".into(),
+ "Ramen (Japan)".into(),
+ "Rendang (Indonesia)".into(),
+ "Sashimi (Japan)".into(),
+ "Satay (Indonesia)".into(),
+ "Shepherd's Pie (Ireland)".into(),
+ "Sushi (Japan)".into(),
+ "Tacos (Mexico)".into(),
+ "Tandoori Chicken (India)".into(),
+ "Tortilla (Spain)".into(),
+ "Tzatziki (Greece)".into(),
+ "Wiener Schnitzel (Austria)".into(),
+ ],
+ selected_ix: 0,
+ },
+ cx,
+ )
+ }),
}
})
}
@@ -159,11 +159,9 @@ impl Render for PickerStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
- let theme = cx.theme();
-
div()
- .bg(theme.styles.colors.background)
+ .bg(cx.theme().styles.colors.background)
.size_full()
- .child(Picker::new("picker_story", self.focus_handle.clone()))
+ .child(self.picker.clone())
}
}
@@ -72,6 +72,8 @@ fn main() {
ThemeSettings::override_global(theme_settings, cx);
ui::settings::init(cx);
+ language::init(cx);
+ editor::init(cx);
let window = cx.open_window(
WindowOptions {