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 #[allow(dead_code)]
46 pub id: Arc<str>,
47 pub name: SharedString,
48}
49
50pub struct ProfilePickerDelegate {
51 profile_picker: WeakEntity<ProfilePicker>,
52 profiles: Vec<ProfileEntry>,
53 matches: Vec<StringMatch>,
54 selected_index: usize,
55}
56
57impl ProfilePickerDelegate {
58 pub fn new(cx: &mut Context<ProfilePicker>) -> Self {
59 let settings = AssistantSettings::get_global(cx);
60
61 let profiles = settings
62 .profiles
63 .iter()
64 .map(|(id, profile)| ProfileEntry {
65 id: id.clone(),
66 name: profile.name.clone(),
67 })
68 .collect::<Vec<_>>();
69
70 Self {
71 profile_picker: cx.entity().downgrade(),
72 profiles,
73 matches: Vec::new(),
74 selected_index: 0,
75 }
76 }
77}
78
79impl PickerDelegate for ProfilePickerDelegate {
80 type ListItem = ListItem;
81
82 fn match_count(&self) -> usize {
83 self.matches.len()
84 }
85
86 fn selected_index(&self) -> usize {
87 self.selected_index
88 }
89
90 fn set_selected_index(
91 &mut self,
92 ix: usize,
93 _window: &mut Window,
94 _cx: &mut Context<Picker<Self>>,
95 ) {
96 self.selected_index = ix;
97 }
98
99 fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
100 "Search profiles…".into()
101 }
102
103 fn update_matches(
104 &mut self,
105 query: String,
106 window: &mut Window,
107 cx: &mut Context<Picker<Self>>,
108 ) -> Task<()> {
109 let background = cx.background_executor().clone();
110 let candidates = self
111 .profiles
112 .iter()
113 .enumerate()
114 .map(|(id, profile)| StringMatchCandidate::new(id, profile.name.as_ref()))
115 .collect::<Vec<_>>();
116
117 cx.spawn_in(window, async move |this, cx| {
118 let matches = if query.is_empty() {
119 candidates
120 .into_iter()
121 .enumerate()
122 .map(|(index, candidate)| StringMatch {
123 candidate_id: index,
124 string: candidate.string,
125 positions: Vec::new(),
126 score: 0.,
127 })
128 .collect()
129 } else {
130 match_strings(
131 &candidates,
132 &query,
133 false,
134 100,
135 &Default::default(),
136 background,
137 )
138 .await
139 };
140
141 this.update(cx, |this, _cx| {
142 this.delegate.matches = matches;
143 this.delegate.selected_index = this
144 .delegate
145 .selected_index
146 .min(this.delegate.matches.len().saturating_sub(1));
147 })
148 .log_err();
149 })
150 }
151
152 fn confirm(&mut self, _secondary: bool, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {
153 }
154
155 fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
156 self.profile_picker
157 .update(cx, |_this, cx| cx.emit(DismissEvent))
158 .log_err();
159 }
160
161 fn render_match(
162 &self,
163 ix: usize,
164 selected: bool,
165 _window: &mut Window,
166 _cx: &mut Context<Picker<Self>>,
167 ) -> Option<Self::ListItem> {
168 let profile_match = &self.matches[ix];
169
170 Some(
171 ListItem::new(ix)
172 .inset(true)
173 .spacing(ListItemSpacing::Sparse)
174 .toggle_state(selected)
175 .child(HighlightedLabel::new(
176 profile_match.string.clone(),
177 profile_match.positions.clone(),
178 )),
179 )
180 }
181}