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}