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