1use fuzzy::StringMatchCandidate;
2use gpui::{div, prelude::*, KeyBinding, Render, SharedString, Styled, Task, View, WindowContext};
3use picker::{Picker, PickerDelegate};
4use std::sync::Arc;
5use ui::{prelude::*, ListItemSpacing};
6use ui::{Label, ListItem};
7
8pub struct PickerStory {
9 picker: View<Picker<Delegate>>,
10}
11
12struct Delegate {
13 candidates: Arc<[StringMatchCandidate]>,
14 matches: Vec<usize>,
15 selected_ix: usize,
16}
17
18impl Delegate {
19 fn new(strings: &[&str]) -> Self {
20 Self {
21 candidates: strings
22 .iter()
23 .copied()
24 .enumerate()
25 .map(|(id, string)| StringMatchCandidate {
26 id,
27 char_bag: string.into(),
28 string: string.into(),
29 })
30 .collect(),
31 matches: vec![],
32 selected_ix: 0,
33 }
34 }
35}
36
37impl PickerDelegate for Delegate {
38 type ListItem = ListItem;
39
40 fn match_count(&self) -> usize {
41 self.candidates.len()
42 }
43
44 fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
45 "Test".into()
46 }
47
48 fn render_match(
49 &self,
50 ix: usize,
51 selected: bool,
52 _cx: &mut gpui::ViewContext<Picker<Self>>,
53 ) -> Option<Self::ListItem> {
54 let candidate_ix = self.matches.get(ix)?;
55 // TASK: Make StringMatchCandidate::string a SharedString
56 let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone());
57
58 Some(
59 ListItem::new(ix)
60 .inset(true)
61 .spacing(ListItemSpacing::Sparse)
62 .selected(selected)
63 .child(Label::new(candidate)),
64 )
65 }
66
67 fn selected_index(&self) -> usize {
68 self.selected_ix
69 }
70
71 fn set_selected_index(&mut self, ix: usize, cx: &mut gpui::ViewContext<Picker<Self>>) {
72 self.selected_ix = ix;
73 cx.notify();
74 }
75
76 fn confirm(&mut self, secondary: bool, _cx: &mut gpui::ViewContext<Picker<Self>>) {
77 let candidate_ix = self.matches[self.selected_ix];
78 let candidate = self.candidates[candidate_ix].string.clone();
79
80 if secondary {
81 eprintln!("Secondary confirmed {}", candidate)
82 } else {
83 eprintln!("Confirmed {}", candidate)
84 }
85 }
86
87 fn dismissed(&mut self, cx: &mut gpui::ViewContext<Picker<Self>>) {
88 cx.quit();
89 }
90
91 fn update_matches(
92 &mut self,
93 query: String,
94 cx: &mut gpui::ViewContext<Picker<Self>>,
95 ) -> Task<()> {
96 let candidates = self.candidates.clone();
97 self.matches = cx
98 .background_executor()
99 .block(fuzzy::match_strings(
100 &candidates,
101 &query,
102 true,
103 100,
104 &Default::default(),
105 cx.background_executor().clone(),
106 ))
107 .into_iter()
108 .map(|r| r.candidate_id)
109 .collect();
110 self.selected_ix = 0;
111 Task::ready(())
112 }
113}
114
115impl PickerStory {
116 pub fn new(cx: &mut WindowContext) -> View<Self> {
117 cx.new_view(|cx| {
118 cx.bind_keys([
119 KeyBinding::new("up", menu::SelectPrev, Some("picker")),
120 KeyBinding::new("pageup", menu::SelectFirst, Some("picker")),
121 KeyBinding::new("shift-pageup", menu::SelectFirst, Some("picker")),
122 KeyBinding::new("ctrl-p", menu::SelectPrev, Some("picker")),
123 KeyBinding::new("down", menu::SelectNext, Some("picker")),
124 KeyBinding::new("pagedown", menu::SelectLast, Some("picker")),
125 KeyBinding::new("shift-pagedown", menu::SelectFirst, Some("picker")),
126 KeyBinding::new("ctrl-n", menu::SelectNext, Some("picker")),
127 KeyBinding::new("cmd-up", menu::SelectFirst, Some("picker")),
128 KeyBinding::new("cmd-down", menu::SelectLast, Some("picker")),
129 KeyBinding::new("enter", menu::Confirm, Some("picker")),
130 KeyBinding::new("ctrl-enter", menu::SecondaryConfirm, Some("picker")),
131 KeyBinding::new("cmd-enter", menu::SecondaryConfirm, Some("picker")),
132 KeyBinding::new("escape", menu::Cancel, Some("picker")),
133 KeyBinding::new("ctrl-c", menu::Cancel, Some("picker")),
134 ]);
135
136 PickerStory {
137 picker: cx.new_view(|cx| {
138 let mut delegate = Delegate::new(&[
139 "Baguette (France)",
140 "Baklava (Turkey)",
141 "Beef Wellington (UK)",
142 "Biryani (India)",
143 "Borscht (Ukraine)",
144 "Bratwurst (Germany)",
145 "Bulgogi (Korea)",
146 "Burrito (USA)",
147 "Ceviche (Peru)",
148 "Chicken Tikka Masala (India)",
149 "Churrasco (Brazil)",
150 "Couscous (North Africa)",
151 "Croissant (France)",
152 "Dim Sum (China)",
153 "Empanada (Argentina)",
154 "Fajitas (Mexico)",
155 "Falafel (Middle East)",
156 "Feijoada (Brazil)",
157 "Fish and Chips (UK)",
158 "Fondue (Switzerland)",
159 "Goulash (Hungary)",
160 "Haggis (Scotland)",
161 "Kebab (Middle East)",
162 "Kimchi (Korea)",
163 "Lasagna (Italy)",
164 "Maple Syrup Pancakes (Canada)",
165 "Moussaka (Greece)",
166 "Pad Thai (Thailand)",
167 "Paella (Spain)",
168 "Pancakes (USA)",
169 "Pasta Carbonara (Italy)",
170 "Pavlova (Australia)",
171 "Peking Duck (China)",
172 "Pho (Vietnam)",
173 "Pierogi (Poland)",
174 "Pizza (Italy)",
175 "Poutine (Canada)",
176 "Pretzel (Germany)",
177 "Ramen (Japan)",
178 "Rendang (Indonesia)",
179 "Sashimi (Japan)",
180 "Satay (Indonesia)",
181 "Shepherd's Pie (Ireland)",
182 "Sushi (Japan)",
183 "Tacos (Mexico)",
184 "Tandoori Chicken (India)",
185 "Tortilla (Spain)",
186 "Tzatziki (Greece)",
187 "Wiener Schnitzel (Austria)",
188 ]);
189 delegate.update_matches("".into(), cx).detach();
190
191 let picker = Picker::uniform_list(delegate, cx);
192 picker.focus(cx);
193 picker
194 }),
195 }
196 })
197 }
198}
199
200impl Render for PickerStory {
201 fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
202 div()
203 .bg(cx.theme().styles.colors.background)
204 .size_full()
205 .child(self.picker.clone())
206 }
207}