1use std::sync::Arc;
2
3use assistant_settings::AssistantSettings;
4use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
5use gpui::{
6 App, Context, DismissEvent, Entity, EventEmitter, Focusable, SharedString, Task, WeakEntity,
7 Window,
8};
9use picker::{Picker, PickerDelegate};
10use settings::Settings;
11use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
12use util::ResultExt as _;
13
14pub struct ProfilePicker {
15 picker: Entity<Picker<ProfilePickerDelegate>>,
16}
17
18impl ProfilePicker {
19 pub fn new(
20 delegate: ProfilePickerDelegate,
21 window: &mut Window,
22 cx: &mut Context<Self>,
23 ) -> Self {
24 let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
25 Self { picker }
26 }
27}
28
29impl EventEmitter<DismissEvent> for ProfilePicker {}
30
31impl Focusable for ProfilePicker {
32 fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
33 self.picker.focus_handle(cx)
34 }
35}
36
37impl Render for ProfilePicker {
38 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
39 v_flex().w(rems(34.)).child(self.picker.clone())
40 }
41}
42
43#[derive(Debug)]
44pub struct ProfileEntry {
45 pub id: Arc<str>,
46 pub name: SharedString,
47}
48
49pub struct ProfilePickerDelegate {
50 profile_picker: WeakEntity<ProfilePicker>,
51 profiles: Vec<ProfileEntry>,
52 matches: Vec<StringMatch>,
53 selected_index: usize,
54 on_confirm: Arc<dyn Fn(&Arc<str>, &mut Window, &mut App) + 'static>,
55}
56
57impl ProfilePickerDelegate {
58 pub fn new(
59 on_confirm: impl Fn(&Arc<str>, &mut Window, &mut App) + 'static,
60 cx: &mut Context<ProfilePicker>,
61 ) -> Self {
62 let settings = AssistantSettings::get_global(cx);
63
64 let profiles = settings
65 .profiles
66 .iter()
67 .map(|(id, profile)| ProfileEntry {
68 id: id.clone(),
69 name: profile.name.clone(),
70 })
71 .collect::<Vec<_>>();
72
73 Self {
74 profile_picker: cx.entity().downgrade(),
75 profiles,
76 matches: Vec::new(),
77 selected_index: 0,
78 on_confirm: Arc::new(on_confirm),
79 }
80 }
81}
82
83impl PickerDelegate for ProfilePickerDelegate {
84 type ListItem = ListItem;
85
86 fn match_count(&self) -> usize {
87 self.matches.len()
88 }
89
90 fn selected_index(&self) -> usize {
91 self.selected_index
92 }
93
94 fn set_selected_index(
95 &mut self,
96 ix: usize,
97 _window: &mut Window,
98 _cx: &mut Context<Picker<Self>>,
99 ) {
100 self.selected_index = ix;
101 }
102
103 fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
104 "Search profiles…".into()
105 }
106
107 fn update_matches(
108 &mut self,
109 query: String,
110 window: &mut Window,
111 cx: &mut Context<Picker<Self>>,
112 ) -> Task<()> {
113 let background = cx.background_executor().clone();
114 let candidates = self
115 .profiles
116 .iter()
117 .enumerate()
118 .map(|(id, profile)| StringMatchCandidate::new(id, profile.name.as_ref()))
119 .collect::<Vec<_>>();
120
121 cx.spawn_in(window, async move |this, cx| {
122 let matches = if query.is_empty() {
123 candidates
124 .into_iter()
125 .enumerate()
126 .map(|(index, candidate)| StringMatch {
127 candidate_id: index,
128 string: candidate.string,
129 positions: Vec::new(),
130 score: 0.,
131 })
132 .collect()
133 } else {
134 match_strings(
135 &candidates,
136 &query,
137 false,
138 100,
139 &Default::default(),
140 background,
141 )
142 .await
143 };
144
145 this.update(cx, |this, _cx| {
146 this.delegate.matches = matches;
147 this.delegate.selected_index = this
148 .delegate
149 .selected_index
150 .min(this.delegate.matches.len().saturating_sub(1));
151 })
152 .log_err();
153 })
154 }
155
156 fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
157 if self.matches.is_empty() {
158 self.dismissed(window, cx);
159 return;
160 }
161
162 let candidate_id = self.matches[self.selected_index].candidate_id;
163 let profile = &self.profiles[candidate_id];
164
165 (self.on_confirm)(&profile.id, window, cx);
166 }
167
168 fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
169 self.profile_picker
170 .update(cx, |_this, cx| cx.emit(DismissEvent))
171 .log_err();
172 }
173
174 fn render_match(
175 &self,
176 ix: usize,
177 selected: bool,
178 _window: &mut Window,
179 _cx: &mut Context<Picker<Self>>,
180 ) -> Option<Self::ListItem> {
181 let profile_match = &self.matches[ix];
182
183 Some(
184 ListItem::new(ix)
185 .inset(true)
186 .spacing(ListItemSpacing::Sparse)
187 .toggle_state(selected)
188 .child(HighlightedLabel::new(
189 profile_match.string.clone(),
190 profile_match.positions.clone(),
191 )),
192 )
193 }
194}