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