profile_picker.rs

  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}