1mod fetch_context_picker;
2mod file_context_picker;
3mod thread_context_picker;
4
5use std::sync::Arc;
6
7use gpui::{
8 AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, SharedString, Task, View,
9 WeakModel, WeakView,
10};
11use picker::{Picker, PickerDelegate};
12use ui::{prelude::*, ListItem, ListItemSpacing};
13use util::ResultExt;
14use workspace::Workspace;
15
16use crate::context_picker::fetch_context_picker::FetchContextPicker;
17use crate::context_picker::file_context_picker::FileContextPicker;
18use crate::context_picker::thread_context_picker::ThreadContextPicker;
19use crate::context_store::ContextStore;
20use crate::thread_store::ThreadStore;
21
22#[derive(Debug, Clone)]
23enum ContextPickerMode {
24 Default,
25 File(View<FileContextPicker>),
26 Fetch(View<FetchContextPicker>),
27 Thread(View<ThreadContextPicker>),
28}
29
30pub(super) struct ContextPicker {
31 mode: ContextPickerMode,
32 picker: View<Picker<ContextPickerDelegate>>,
33}
34
35impl ContextPicker {
36 pub fn new(
37 workspace: WeakView<Workspace>,
38 thread_store: Option<WeakModel<ThreadStore>>,
39 context_store: WeakModel<ContextStore>,
40 cx: &mut ViewContext<Self>,
41 ) -> Self {
42 let mut entries = vec![
43 ContextPickerEntry {
44 name: "Directory".into(),
45 icon: IconName::Folder,
46 },
47 ContextPickerEntry {
48 name: "File".into(),
49 icon: IconName::File,
50 },
51 ContextPickerEntry {
52 name: "Fetch".into(),
53 icon: IconName::Globe,
54 },
55 ];
56
57 if thread_store.is_some() {
58 entries.push(ContextPickerEntry {
59 name: "Thread".into(),
60 icon: IconName::MessageCircle,
61 });
62 }
63
64 let delegate = ContextPickerDelegate {
65 context_picker: cx.view().downgrade(),
66 workspace,
67 thread_store,
68 context_store,
69 entries,
70 selected_ix: 0,
71 };
72
73 let picker = cx.new_view(|cx| {
74 Picker::nonsearchable_uniform_list(delegate, cx).max_height(Some(rems(20.).into()))
75 });
76
77 ContextPicker {
78 mode: ContextPickerMode::Default,
79 picker,
80 }
81 }
82
83 pub fn reset_mode(&mut self) {
84 self.mode = ContextPickerMode::Default;
85 }
86}
87
88impl EventEmitter<DismissEvent> for ContextPicker {}
89
90impl FocusableView for ContextPicker {
91 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
92 match &self.mode {
93 ContextPickerMode::Default => self.picker.focus_handle(cx),
94 ContextPickerMode::File(file_picker) => file_picker.focus_handle(cx),
95 ContextPickerMode::Fetch(fetch_picker) => fetch_picker.focus_handle(cx),
96 ContextPickerMode::Thread(thread_picker) => thread_picker.focus_handle(cx),
97 }
98 }
99}
100
101impl Render for ContextPicker {
102 fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
103 v_flex()
104 .w(px(400.))
105 .min_w(px(400.))
106 .map(|parent| match &self.mode {
107 ContextPickerMode::Default => parent.child(self.picker.clone()),
108 ContextPickerMode::File(file_picker) => parent.child(file_picker.clone()),
109 ContextPickerMode::Fetch(fetch_picker) => parent.child(fetch_picker.clone()),
110 ContextPickerMode::Thread(thread_picker) => parent.child(thread_picker.clone()),
111 })
112 }
113}
114
115#[derive(Clone)]
116struct ContextPickerEntry {
117 name: SharedString,
118 icon: IconName,
119}
120
121pub(crate) struct ContextPickerDelegate {
122 context_picker: WeakView<ContextPicker>,
123 workspace: WeakView<Workspace>,
124 thread_store: Option<WeakModel<ThreadStore>>,
125 context_store: WeakModel<ContextStore>,
126 entries: Vec<ContextPickerEntry>,
127 selected_ix: usize,
128}
129
130impl PickerDelegate for ContextPickerDelegate {
131 type ListItem = ListItem;
132
133 fn match_count(&self) -> usize {
134 self.entries.len()
135 }
136
137 fn selected_index(&self) -> usize {
138 self.selected_ix
139 }
140
141 fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
142 self.selected_ix = ix.min(self.entries.len().saturating_sub(1));
143 cx.notify();
144 }
145
146 fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
147 "Select a context source…".into()
148 }
149
150 fn update_matches(&mut self, _query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
151 Task::ready(())
152 }
153
154 fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
155 if let Some(entry) = self.entries.get(self.selected_ix) {
156 self.context_picker
157 .update(cx, |this, cx| {
158 match entry.name.to_string().as_str() {
159 "File" => {
160 this.mode = ContextPickerMode::File(cx.new_view(|cx| {
161 FileContextPicker::new(
162 self.context_picker.clone(),
163 self.workspace.clone(),
164 self.context_store.clone(),
165 cx,
166 )
167 }));
168 }
169 "Fetch" => {
170 this.mode = ContextPickerMode::Fetch(cx.new_view(|cx| {
171 FetchContextPicker::new(
172 self.context_picker.clone(),
173 self.workspace.clone(),
174 self.context_store.clone(),
175 cx,
176 )
177 }));
178 }
179 "Thread" => {
180 if let Some(thread_store) = self.thread_store.as_ref() {
181 this.mode = ContextPickerMode::Thread(cx.new_view(|cx| {
182 ThreadContextPicker::new(
183 thread_store.clone(),
184 self.context_picker.clone(),
185 self.context_store.clone(),
186 cx,
187 )
188 }));
189 }
190 }
191 _ => {}
192 }
193
194 cx.focus_self();
195 })
196 .log_err();
197 }
198 }
199
200 fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
201 self.context_picker
202 .update(cx, |this, cx| match this.mode {
203 ContextPickerMode::Default => cx.emit(DismissEvent),
204 ContextPickerMode::File(_)
205 | ContextPickerMode::Fetch(_)
206 | ContextPickerMode::Thread(_) => {}
207 })
208 .log_err();
209 }
210
211 fn render_match(
212 &self,
213 ix: usize,
214 selected: bool,
215 _cx: &mut ViewContext<Picker<Self>>,
216 ) -> Option<Self::ListItem> {
217 let entry = &self.entries[ix];
218
219 Some(
220 ListItem::new(ix)
221 .inset(true)
222 .spacing(ListItemSpacing::Dense)
223 .toggle_state(selected)
224 .child(
225 h_flex()
226 .min_w(px(250.))
227 .max_w(px(400.))
228 .gap_2()
229 .child(Icon::new(entry.icon).size(IconSize::Small))
230 .child(Label::new(entry.name.clone()).single_line()),
231 ),
232 )
233 }
234}