Snapshot for kirill

Mikayla created

Change summary

Cargo.lock                        |  17 +
Cargo.toml                        |   1 
crates/gpui2/src/elements.rs      |   1 
crates/gpui2/src/elements/img.rs  |   2 
crates/gpui2/src/elements/list.rs |  91 +++++++
crates/picker2/Cargo.toml         |  28 ++
crates/picker2/src/picker2.rs     | 400 +++++++++++++++++++++++++++++++++
7 files changed, 540 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -6006,6 +6006,23 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "picker2"
+version = "0.1.0"
+dependencies = [
+ "ctor",
+ "editor2",
+ "env_logger 0.9.3",
+ "gpui2",
+ "menu2",
+ "parking_lot 0.11.2",
+ "serde_json",
+ "settings2",
+ "theme2",
+ "util",
+ "workspace2",
+]
+
 [[package]]
 name = "pico-args"
 version = "0.4.2"

Cargo.toml 🔗

@@ -67,6 +67,7 @@ members = [
     "crates/notifications",
     "crates/outline",
     "crates/picker",
+    "crates/picker2",
     "crates/plugin",
     "crates/plugin_macros",
     "crates/plugin_runtime",

crates/gpui2/src/elements/img.rs 🔗

@@ -1,3 +1,5 @@
+use std::sync::Arc;
+
 use crate::{
     div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus,
     ElementId, ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable,

crates/gpui2/src/elements/list.rs 🔗

@@ -0,0 +1,91 @@
+use std::ops::Range;
+
+use smallvec::SmallVec;
+
+use crate::{AnyElement, Component, Element, ElementId, StyleRefinement, ViewContext};
+
+// We want to support uniform and non-uniform height
+// We need to make the ID mandatory, to replace the 'state' field
+// Previous implementation measured the first element as early as possible
+
+fn list<'a, Id, V, Iter, C>(
+    id: Id,
+    f: impl 'static + FnOnce(&'a mut V, Range<usize>, &'a mut ViewContext<V>) -> Iter,
+) -> List<V>
+where
+    Id: Into<ElementId>,
+    V: 'static,
+    Iter: 'a + Iterator<Item = C>,
+    C: Component<V>,
+{
+    List {
+        id: id.into(),
+        render_items: Box::new(|view, visible_range, cx| {
+            f(view, visible_range, cx)
+                .map(|element| element.render())
+                .collect()
+        }),
+    }
+}
+
+struct List<V> {
+    id: ElementId,
+    render_items: Box<
+        dyn for<'a> FnOnce(
+            &'a mut V,
+            Range<usize>,
+            &'a mut ViewContext<V>,
+        ) -> SmallVec<[AnyElement<V>; 64]>,
+    >,
+}
+
+impl<V> List<V> {}
+
+// #[derive(Debug)]
+// pub enum ScrollTarget {
+//     Show(usize),
+//     Center(usize),
+// }
+
+#[derive(Default)]
+struct ListState {
+    scroll_top: f32,
+    style: StyleRefinement,
+    // todo
+    // scroll_to: Option<ScrollTarget>,
+}
+impl<V: 'static> Element<V> for List<V> {
+    type ElementState = ListState;
+
+    fn id(&self) -> Option<crate::ElementId> {
+        Some(self.id)
+    }
+
+    fn initialize(
+        &mut self,
+        _: &mut V,
+        element_state: Option<Self::ElementState>,
+        _: &mut crate::ViewContext<V>,
+    ) -> Self::ElementState {
+        let element_state = element_state.unwrap_or_default();
+        element_state
+    }
+
+    fn layout(
+        &mut self,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut crate::ViewContext<V>,
+    ) -> crate::LayoutId {
+        todo!()
+    }
+
+    fn paint(
+        &mut self,
+        bounds: crate::Bounds<crate::Pixels>,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut crate::ViewContext<V>,
+    ) {
+    }
+}

crates/picker2/Cargo.toml 🔗

@@ -0,0 +1,28 @@
+[package]
+name = "picker2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/picker2.rs"
+doctest = false
+
+[dependencies]
+editor = { package = "editor2", path = "../editor2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+menu = { package = "menu2", path = "../menu2" }
+settings = { package = "settings2", path = "../settings2" }
+util = { path = "../util" }
+theme = { package = "theme2", path = "../theme2" }
+workspace = { package = "workspace2", path = "../workspace2" }
+
+parking_lot.workspace = true
+
+[dev-dependencies]
+editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
+gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
+serde_json.workspace = true
+workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+ctor.workspace = true
+env_logger.workspace = true

crates/picker2/src/picker2.rs 🔗

@@ -0,0 +1,400 @@
+// use editor::Editor;
+// use gpui::{
+//     elements::*,
+//     geometry::vector::{vec2f, Vector2F},
+//     keymap_matcher::KeymapContext,
+//     platform::{CursorStyle, MouseButton},
+//     AnyElement, AnyViewHandle, AppContext, Axis, Entity, MouseState, Task, View, ViewContext,
+//     ViewHandle,
+// };
+// use menu::{Cancel, Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
+// use parking_lot::Mutex;
+// use std::{cmp, sync::Arc};
+// use util::ResultExt;
+// use workspace::Modal;
+
+// #[derive(Clone, Copy)]
+// pub enum PickerEvent {
+//     Dismiss,
+// }
+
+use std::ops::Range;
+
+use gpui::{div, AppContext, Component, Div, Element, ParentElement, Render, ViewContext};
+
+pub struct Picker<D> {
+    delegate: D,
+    //     query_editor: ViewHandle<Editor>,
+    //     list_state: UniformListState,
+    //     max_size: Vector2F,
+    //     theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>,
+    //     confirmed: bool,
+    //     pending_update_matches: Option<Task<Option<()>>>,
+    //     confirm_on_update: Option<bool>,
+    //     has_focus: bool,
+}
+
+pub trait PickerDelegate: Sized + 'static {
+    type ListItem: Element<Picker<Self>>;
+    //     fn placeholder_text(&self) -> Arc<str>;
+    //     fn match_count(&self) -> usize;
+    //     fn selected_index(&self) -> usize;
+    //     fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
+    //     fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
+    //     fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
+    //     fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
+
+    // todo!("rename to render_candidate?")
+    fn render_match(
+        &self,
+        ix: usize,
+        active: bool,
+        hovered: bool,
+        selected: bool,
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Self::ListItem;
+
+    //     fn center_selection_after_match_updates(&self) -> bool {
+    //         false
+    //     }
+    //     fn render_header(
+    //         &self,
+    //         _cx: &mut ViewContext<Picker<Self>>,
+    //     ) -> Option<AnyElement<Picker<Self>>> {
+    //         None
+    //     }
+    //     fn render_footer(
+    //         &self,
+    //         _cx: &mut ViewContext<Picker<Self>>,
+    //     ) -> Option<AnyElement<Picker<Self>>> {
+    //         None
+    //     }
+}
+
+// impl<D: PickerDelegate> Entity for Picker<D> {
+//     type Event = PickerEvent;
+// }
+
+impl<D: PickerDelegate> Render for Picker<D> {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
+        div().child(list(
+            "candidates",
+            |this: &mut Picker<D>, visible_range, cx| {
+                visible_range
+                    .into_iter()
+                    .map(|ix| this.delegate.render_match(ix, false, false, false, cx))
+            },
+        ))
+    }
+}
+
+fn list<'a, D: PickerDelegate, F, I>(id: &'static str, f: F) -> Div<Picker<D>>
+where
+    F: FnOnce(&'a mut Picker<D>, Range<usize>, &'a mut ViewContext<Picker<D>>) -> I,
+    I: 'a + Iterator<Item = D::ListItem>,
+{
+    todo!();
+}
+
+// impl<D: PickerDelegate> View for Picker<D> {
+//     fn ui_name() -> &'static str {
+//         "Picker"
+//     }
+
+//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+//         let theme = (self.theme.lock())(theme::current(cx).as_ref());
+//         let query = self.query(cx);
+//         let match_count = self.delegate.match_count();
+
+//         let container_style;
+//         let editor_style;
+//         if query.is_empty() && match_count == 0 {
+//             container_style = theme.empty_container;
+//             editor_style = theme.empty_input_editor.container;
+//         } else {
+//             container_style = theme.container;
+//             editor_style = theme.input_editor.container;
+//         };
+
+//         Flex::new(Axis::Vertical)
+//             .with_child(
+//                 ChildView::new(&self.query_editor, cx)
+//                     .contained()
+//                     .with_style(editor_style),
+//             )
+//             .with_children(self.delegate.render_header(cx))
+//             .with_children(if match_count == 0 {
+//                 if query.is_empty() {
+//                     None
+//                 } else {
+//                     Some(
+//                         Label::new("No matches", theme.no_matches.label.clone())
+//                             .contained()
+//                             .with_style(theme.no_matches.container)
+//                             .into_any(),
+//                     )
+//                 }
+//             } else {
+//                 Some(
+//                     UniformList::new(
+//                         self.list_state.clone(),
+//                         match_count,
+//                         cx,
+//                         move |this, mut range, items, cx| {
+//                             let selected_ix = this.delegate.selected_index();
+//                             range.end = cmp::min(range.end, this.delegate.match_count());
+//                             items.extend(range.map(move |ix| {
+//                                 MouseEventHandler::new::<D, _>(ix, cx, |state, cx| {
+//                                     this.delegate.render_match(ix, state, ix == selected_ix, cx)
+//                                 })
+//                                 // Capture mouse events
+//                                 .on_down(MouseButton::Left, |_, _, _| {})
+//                                 .on_up(MouseButton::Left, |_, _, _| {})
+//                                 .on_click(MouseButton::Left, move |click, picker, cx| {
+//                                     picker.select_index(ix, click.cmd, cx);
+//                                 })
+//                                 .with_cursor_style(CursorStyle::PointingHand)
+//                                 .into_any()
+//                             }));
+//                         },
+//                     )
+//                     .contained()
+//                     .with_margin_top(6.0)
+//                     .flex(1., false)
+//                     .into_any(),
+//                 )
+//             })
+//             .with_children(self.delegate.render_footer(cx))
+//             .contained()
+//             .with_style(container_style)
+//             .constrained()
+//             .with_max_width(self.max_size.x())
+//             .with_max_height(self.max_size.y())
+//             .into_any_named("picker")
+//     }
+
+//     fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
+//         Self::reset_to_default_keymap_context(keymap);
+//         keymap.add_identifier("menu");
+//     }
+
+//     fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+//         self.has_focus = true;
+//         if cx.is_self_focused() {
+//             cx.focus(&self.query_editor);
+//         }
+//     }
+
+//     fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {
+//         self.has_focus = false;
+//     }
+// }
+
+// impl<D: PickerDelegate> Modal for Picker<D> {
+//     fn has_focus(&self) -> bool {
+//         self.has_focus
+//     }
+
+//     fn dismiss_on_event(event: &Self::Event) -> bool {
+//         matches!(event, PickerEvent::Dismiss)
+//     }
+// }
+
+// impl<D: PickerDelegate> Picker<D> {
+//     pub fn init(cx: &mut AppContext) {
+//         cx.add_action(Self::select_first);
+//         cx.add_action(Self::select_last);
+//         cx.add_action(Self::select_next);
+//         cx.add_action(Self::select_prev);
+//         cx.add_action(Self::confirm);
+//         cx.add_action(Self::secondary_confirm);
+//         cx.add_action(Self::cancel);
+//     }
+
+//     pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
+//         let theme = Arc::new(Mutex::new(
+//             Box::new(|theme: &theme::Theme| theme.picker.clone())
+//                 as Box<dyn Fn(&theme::Theme) -> theme::Picker>,
+//         ));
+//         let placeholder_text = delegate.placeholder_text();
+//         let query_editor = cx.add_view({
+//             let picker_theme = theme.clone();
+//             |cx| {
+//                 let mut editor = Editor::single_line(
+//                     Some(Arc::new(move |theme| {
+//                         (picker_theme.lock())(theme).input_editor.clone()
+//                     })),
+//                     cx,
+//                 );
+//                 editor.set_placeholder_text(placeholder_text, cx);
+//                 editor
+//             }
+//         });
+//         cx.subscribe(&query_editor, Self::on_query_editor_event)
+//             .detach();
+//         let mut this = Self {
+//             query_editor,
+//             list_state: Default::default(),
+//             delegate,
+//             max_size: vec2f(540., 420.),
+//             theme,
+//             confirmed: false,
+//             pending_update_matches: None,
+//             confirm_on_update: None,
+//             has_focus: false,
+//         };
+//         this.update_matches(String::new(), cx);
+//         this
+//     }
+
+//     pub fn with_max_size(mut self, width: f32, height: f32) -> Self {
+//         self.max_size = vec2f(width, height);
+//         self
+//     }
+
+//     pub fn with_theme<F>(self, theme: F) -> Self
+//     where
+//         F: 'static + Fn(&theme::Theme) -> theme::Picker,
+//     {
+//         *self.theme.lock() = Box::new(theme);
+//         self
+//     }
+
+//     pub fn delegate(&self) -> &D {
+//         &self.delegate
+//     }
+
+//     pub fn delegate_mut(&mut self) -> &mut D {
+//         &mut self.delegate
+//     }
+
+//     pub fn query(&self, cx: &AppContext) -> String {
+//         self.query_editor.read(cx).text(cx)
+//     }
+
+//     pub fn set_query(&self, query: impl Into<Arc<str>>, cx: &mut ViewContext<Self>) {
+//         self.query_editor
+//             .update(cx, |editor, cx| editor.set_text(query, cx));
+//     }
+
+//     fn on_query_editor_event(
+//         &mut self,
+//         _: ViewHandle<Editor>,
+//         event: &editor::Event,
+//         cx: &mut ViewContext<Self>,
+//     ) {
+//         match event {
+//             editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx),
+//             editor::Event::Blurred if !self.confirmed => {
+//                 self.dismiss(cx);
+//             }
+//             _ => {}
+//         }
+//     }
+
+//     pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
+//         let update = self.delegate.update_matches(query, cx);
+//         self.matches_updated(cx);
+//         self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move {
+//             update.await;
+//             this.update(&mut cx, |this, cx| {
+//                 this.matches_updated(cx);
+//             })
+//             .log_err()
+//         }));
+//     }
+
+//     fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
+//         let index = self.delegate.selected_index();
+//         let target = if self.delegate.center_selection_after_match_updates() {
+//             ScrollTarget::Center(index)
+//         } else {
+//             ScrollTarget::Show(index)
+//         };
+//         self.list_state.scroll_to(target);
+//         self.pending_update_matches = None;
+//         if let Some(secondary) = self.confirm_on_update.take() {
+//             self.confirmed = true;
+//             self.delegate.confirm(secondary, cx)
+//         }
+//         cx.notify();
+//     }
+
+//     pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
+//         if self.delegate.match_count() > 0 {
+//             self.delegate.set_selected_index(0, cx);
+//             self.list_state.scroll_to(ScrollTarget::Show(0));
+//         }
+
+//         cx.notify();
+//     }
+
+//     pub fn select_index(&mut self, index: usize, cmd: bool, cx: &mut ViewContext<Self>) {
+//         if self.delegate.match_count() > 0 {
+//             self.confirmed = true;
+//             self.delegate.set_selected_index(index, cx);
+//             self.delegate.confirm(cmd, cx);
+//         }
+//     }
+
+//     pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
+//         let match_count = self.delegate.match_count();
+//         if match_count > 0 {
+//             let index = match_count - 1;
+//             self.delegate.set_selected_index(index, cx);
+//             self.list_state.scroll_to(ScrollTarget::Show(index));
+//         }
+//         cx.notify();
+//     }
+
+//     pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
+//         let next_index = self.delegate.selected_index() + 1;
+//         if next_index < self.delegate.match_count() {
+//             self.delegate.set_selected_index(next_index, cx);
+//             self.list_state.scroll_to(ScrollTarget::Show(next_index));
+//         }
+
+//         cx.notify();
+//     }
+
+//     pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
+//         let mut selected_index = self.delegate.selected_index();
+//         if selected_index > 0 {
+//             selected_index -= 1;
+//             self.delegate.set_selected_index(selected_index, cx);
+//             self.list_state
+//                 .scroll_to(ScrollTarget::Show(selected_index));
+//         }
+
+//         cx.notify();
+//     }
+
+//     pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
+//         if self.pending_update_matches.is_some() {
+//             self.confirm_on_update = Some(false)
+//         } else {
+//             self.confirmed = true;
+//             self.delegate.confirm(false, cx);
+//         }
+//     }
+
+//     pub fn secondary_confirm(&mut self, _: &SecondaryConfirm, cx: &mut ViewContext<Self>) {
+//         if self.pending_update_matches.is_some() {
+//             self.confirm_on_update = Some(true)
+//         } else {
+//             self.confirmed = true;
+//             self.delegate.confirm(true, cx);
+//         }
+//     }
+
+//     fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
+//         self.dismiss(cx);
+//     }
+
+//     fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+//         cx.emit(PickerEvent::Dismiss);
+//         self.delegate.dismissed(cx);
+//     }
+// }