1// TODO: Remove this when we finish the implementation.
2#![allow(unused)]
3
4use std::path::Path;
5use std::sync::atomic::AtomicBool;
6use std::sync::Arc;
7
8use fuzzy::PathMatch;
9use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
10use picker::{Picker, PickerDelegate};
11use project::{PathMatchCandidateSet, WorktreeId};
12use ui::{prelude::*, ListItem};
13use util::ResultExt as _;
14use workspace::Workspace;
15
16use crate::context::ContextKind;
17use crate::context_picker::{ConfirmBehavior, ContextPicker};
18use crate::context_store::ContextStore;
19
20pub struct DirectoryContextPicker {
21 picker: View<Picker<DirectoryContextPickerDelegate>>,
22}
23
24impl DirectoryContextPicker {
25 pub fn new(
26 context_picker: WeakView<ContextPicker>,
27 workspace: WeakView<Workspace>,
28 context_store: WeakModel<ContextStore>,
29 confirm_behavior: ConfirmBehavior,
30 cx: &mut ViewContext<Self>,
31 ) -> Self {
32 let delegate = DirectoryContextPickerDelegate::new(
33 context_picker,
34 workspace,
35 context_store,
36 confirm_behavior,
37 );
38 let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
39
40 Self { picker }
41 }
42}
43
44impl FocusableView for DirectoryContextPicker {
45 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
46 self.picker.focus_handle(cx)
47 }
48}
49
50impl Render for DirectoryContextPicker {
51 fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
52 self.picker.clone()
53 }
54}
55
56pub struct DirectoryContextPickerDelegate {
57 context_picker: WeakView<ContextPicker>,
58 workspace: WeakView<Workspace>,
59 context_store: WeakModel<ContextStore>,
60 confirm_behavior: ConfirmBehavior,
61 matches: Vec<PathMatch>,
62 selected_index: usize,
63}
64
65impl DirectoryContextPickerDelegate {
66 pub fn new(
67 context_picker: WeakView<ContextPicker>,
68 workspace: WeakView<Workspace>,
69 context_store: WeakModel<ContextStore>,
70 confirm_behavior: ConfirmBehavior,
71 ) -> Self {
72 Self {
73 context_picker,
74 workspace,
75 context_store,
76 confirm_behavior,
77 matches: Vec::new(),
78 selected_index: 0,
79 }
80 }
81
82 fn search(
83 &mut self,
84 query: String,
85 cancellation_flag: Arc<AtomicBool>,
86 workspace: &View<Workspace>,
87 cx: &mut ViewContext<Picker<Self>>,
88 ) -> Task<Vec<PathMatch>> {
89 if query.is_empty() {
90 let workspace = workspace.read(cx);
91 let project = workspace.project().read(cx);
92 let directory_matches = project.worktrees(cx).flat_map(|worktree| {
93 let worktree = worktree.read(cx);
94 let path_prefix: Arc<str> = worktree.root_name().into();
95 worktree.directories(false, 0).map(move |entry| PathMatch {
96 score: 0.,
97 positions: Vec::new(),
98 worktree_id: worktree.id().to_usize(),
99 path: entry.path.clone(),
100 path_prefix: path_prefix.clone(),
101 distance_to_relative_ancestor: 0,
102 is_dir: true,
103 })
104 });
105
106 Task::ready(directory_matches.collect())
107 } else {
108 let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
109 let candidate_sets = worktrees
110 .into_iter()
111 .map(|worktree| {
112 let worktree = worktree.read(cx);
113
114 PathMatchCandidateSet {
115 snapshot: worktree.snapshot(),
116 include_ignored: worktree
117 .root_entry()
118 .map_or(false, |entry| entry.is_ignored),
119 include_root_name: true,
120 candidates: project::Candidates::Directories,
121 }
122 })
123 .collect::<Vec<_>>();
124
125 let executor = cx.background_executor().clone();
126 cx.foreground_executor().spawn(async move {
127 fuzzy::match_path_sets(
128 candidate_sets.as_slice(),
129 query.as_str(),
130 None,
131 false,
132 100,
133 &cancellation_flag,
134 executor,
135 )
136 .await
137 })
138 }
139 }
140}
141
142impl PickerDelegate for DirectoryContextPickerDelegate {
143 type ListItem = ListItem;
144
145 fn match_count(&self) -> usize {
146 self.matches.len()
147 }
148
149 fn selected_index(&self) -> usize {
150 self.selected_index
151 }
152
153 fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
154 self.selected_index = ix;
155 }
156
157 fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
158 "Search folders…".into()
159 }
160
161 fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
162 let Some(workspace) = self.workspace.upgrade() else {
163 return Task::ready(());
164 };
165
166 let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
167
168 cx.spawn(|this, mut cx| async move {
169 let mut paths = search_task.await;
170 let empty_path = Path::new("");
171 paths.retain(|path_match| path_match.path.as_ref() != empty_path);
172
173 this.update(&mut cx, |this, _cx| {
174 this.delegate.matches = paths;
175 })
176 .log_err();
177 })
178 }
179
180 fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
181 let Some(mat) = self.matches.get(self.selected_index) else {
182 return;
183 };
184
185 let workspace = self.workspace.clone();
186 let Some(project) = workspace
187 .upgrade()
188 .map(|workspace| workspace.read(cx).project().clone())
189 else {
190 return;
191 };
192 let path = mat.path.clone();
193 let worktree_id = WorktreeId::from_usize(mat.worktree_id);
194 let confirm_behavior = self.confirm_behavior;
195 cx.spawn(|this, mut cx| async move {
196 this.update(&mut cx, |this, cx| {
197 let mut text = String::new();
198
199 // TODO: Add the files from the selected directory.
200
201 this.delegate
202 .context_store
203 .update(cx, |context_store, cx| {
204 context_store.insert_context(
205 ContextKind::Directory,
206 path.to_string_lossy().to_string(),
207 text,
208 );
209 })?;
210
211 match confirm_behavior {
212 ConfirmBehavior::KeepOpen => {}
213 ConfirmBehavior::Close => this.delegate.dismissed(cx),
214 }
215
216 anyhow::Ok(())
217 })??;
218
219 anyhow::Ok(())
220 })
221 .detach_and_log_err(cx)
222 }
223
224 fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
225 self.context_picker
226 .update(cx, |this, cx| {
227 this.reset_mode();
228 cx.emit(DismissEvent);
229 })
230 .ok();
231 }
232
233 fn render_match(
234 &self,
235 ix: usize,
236 selected: bool,
237 _cx: &mut ViewContext<Picker<Self>>,
238 ) -> Option<Self::ListItem> {
239 let path_match = &self.matches[ix];
240 let directory_name = path_match.path.to_string_lossy().to_string();
241
242 Some(
243 ListItem::new(ix)
244 .inset(true)
245 .toggle_state(selected)
246 .child(h_flex().gap_2().child(Label::new(directory_name))),
247 )
248 }
249}