saved_conversations.rs

  1use std::sync::Arc;
  2
  3use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
  4use gpui::{AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, View, WeakView};
  5use picker::{Picker, PickerDelegate};
  6use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
  7use util::ResultExt;
  8
  9use crate::saved_conversation::SavedConversationMetadata;
 10
 11pub struct SavedConversations {
 12    focus_handle: FocusHandle,
 13    picker: Option<View<Picker<SavedConversationPickerDelegate>>>,
 14}
 15
 16impl EventEmitter<DismissEvent> for SavedConversations {}
 17
 18impl FocusableView for SavedConversations {
 19    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
 20        if let Some(picker) = self.picker.as_ref() {
 21            picker.focus_handle(cx)
 22        } else {
 23            self.focus_handle.clone()
 24        }
 25    }
 26}
 27
 28impl SavedConversations {
 29    pub fn new(cx: &mut ViewContext<Self>) -> Self {
 30        Self {
 31            focus_handle: cx.focus_handle(),
 32            picker: None,
 33        }
 34    }
 35
 36    pub fn init(
 37        &mut self,
 38        saved_conversations: Vec<SavedConversationMetadata>,
 39        cx: &mut ViewContext<Self>,
 40    ) {
 41        let delegate =
 42            SavedConversationPickerDelegate::new(cx.view().downgrade(), saved_conversations);
 43        self.picker = Some(cx.new_view(|cx| Picker::uniform_list(delegate, cx).modal(false)));
 44    }
 45}
 46
 47impl Render for SavedConversations {
 48    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 49        v_flex()
 50            .w_full()
 51            .bg(cx.theme().colors().panel_background)
 52            .children(self.picker.clone())
 53    }
 54}
 55
 56pub struct SavedConversationPickerDelegate {
 57    view: WeakView<SavedConversations>,
 58    saved_conversations: Vec<SavedConversationMetadata>,
 59    selected_index: usize,
 60    matches: Vec<StringMatch>,
 61}
 62
 63impl SavedConversationPickerDelegate {
 64    pub fn new(
 65        weak_view: WeakView<SavedConversations>,
 66        saved_conversations: Vec<SavedConversationMetadata>,
 67    ) -> Self {
 68        let matches = saved_conversations
 69            .iter()
 70            .map(|conversation| StringMatch {
 71                candidate_id: 0,
 72                score: 0.0,
 73                positions: Default::default(),
 74                string: conversation.title.clone(),
 75            })
 76            .collect();
 77
 78        Self {
 79            view: weak_view,
 80            saved_conversations,
 81            selected_index: 0,
 82            matches,
 83        }
 84    }
 85}
 86
 87impl PickerDelegate for SavedConversationPickerDelegate {
 88    type ListItem = ui::ListItem;
 89
 90    fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
 91        "Select saved conversation...".into()
 92    }
 93
 94    fn match_count(&self) -> usize {
 95        self.matches.len()
 96    }
 97
 98    fn selected_index(&self) -> usize {
 99        self.selected_index
100    }
101
102    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
103        self.selected_index = ix;
104    }
105
106    fn update_matches(
107        &mut self,
108        query: String,
109        cx: &mut ViewContext<Picker<Self>>,
110    ) -> gpui::Task<()> {
111        let background_executor = cx.background_executor().clone();
112        let candidates = self
113            .saved_conversations
114            .iter()
115            .enumerate()
116            .map(|(id, conversation)| {
117                let text = conversation.title.clone();
118
119                StringMatchCandidate {
120                    id,
121                    char_bag: text.as_str().into(),
122                    string: text,
123                }
124            })
125            .collect::<Vec<_>>();
126
127        cx.spawn(move |this, mut cx| async move {
128            let matches = if query.is_empty() {
129                candidates
130                    .into_iter()
131                    .enumerate()
132                    .map(|(index, candidate)| StringMatch {
133                        candidate_id: index,
134                        string: candidate.string,
135                        positions: Vec::new(),
136                        score: 0.0,
137                    })
138                    .collect()
139            } else {
140                match_strings(
141                    &candidates,
142                    &query,
143                    false,
144                    100,
145                    &Default::default(),
146                    background_executor,
147                )
148                .await
149            };
150
151            this.update(&mut cx, |this, _cx| {
152                this.delegate.matches = matches;
153                this.delegate.selected_index = this
154                    .delegate
155                    .selected_index
156                    .min(this.delegate.matches.len().saturating_sub(1));
157            })
158            .log_err();
159        })
160    }
161
162    fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
163        if self.matches.is_empty() {
164            self.dismissed(cx);
165            return;
166        }
167
168        // TODO: Implement selecting a saved conversation.
169    }
170
171    fn dismissed(&mut self, cx: &mut ui::prelude::ViewContext<Picker<Self>>) {
172        self.view
173            .update(cx, |_, cx| cx.emit(DismissEvent))
174            .log_err();
175    }
176
177    fn render_match(
178        &self,
179        ix: usize,
180        selected: bool,
181        _cx: &mut ViewContext<Picker<Self>>,
182    ) -> Option<Self::ListItem> {
183        let conversation_match = &self.matches[ix];
184        let _conversation = &self.saved_conversations[conversation_match.candidate_id];
185
186        Some(
187            ListItem::new(ix)
188                .spacing(ListItemSpacing::Sparse)
189                .selected(selected)
190                .child(HighlightedLabel::new(
191                    conversation_match.string.clone(),
192                    conversation_match.positions.clone(),
193                )),
194        )
195    }
196}