1pub use super::fuzzy::PathMatch;
2use super::{
3 char_bag::CharBag,
4 fuzzy::{self, PathEntry},
5};
6use crate::{editor::History, timer, util::post_inc};
7use anyhow::{anyhow, Result};
8use crossbeam_channel as channel;
9use easy_parallel::Parallel;
10use gpui::{AppContext, Entity, ModelContext, ModelHandle};
11use ignore::dir::{Ignore, IgnoreBuilder};
12use parking_lot::RwLock;
13use smol::prelude::*;
14use std::{
15 collections::HashMap,
16 ffi::{OsStr, OsString},
17 fmt, fs, io,
18 os::unix::fs::MetadataExt,
19 path::Path,
20 path::PathBuf,
21 sync::Arc,
22 time::Duration,
23};
24
25#[derive(Clone)]
26pub struct Worktree(Arc<RwLock<WorktreeState>>);
27
28struct WorktreeState {
29 id: usize,
30 path: PathBuf,
31 entries: Vec<Entry>,
32 file_paths: Vec<PathEntry>,
33 histories: HashMap<usize, History>,
34 scanning: bool,
35}
36
37struct DirToScan {
38 id: usize,
39 path: PathBuf,
40 relative_path: PathBuf,
41 ignore: Option<Ignore>,
42 dirs_to_scan: channel::Sender<io::Result<DirToScan>>,
43}
44
45impl Worktree {
46 pub fn new<T>(id: usize, path: T, ctx: Option<&mut ModelContext<Self>>) -> Self
47 where
48 T: Into<PathBuf>,
49 {
50 let tree = Self(Arc::new(RwLock::new(WorktreeState {
51 id,
52 path: path.into(),
53 entries: Vec::new(),
54 file_paths: Vec::new(),
55 histories: HashMap::new(),
56 scanning: ctx.is_some(),
57 })));
58
59 if let Some(ctx) = ctx {
60 tree.0.write().scanning = true;
61
62 let tree = tree.clone();
63 let (tx, rx) = smol::channel::bounded(1);
64
65 let task = ctx.background_executor().spawn(async move {
66 let _ = tx.send(tree.scan_dirs()?).await;
67 Ok(())
68 });
69
70 ctx.spawn(task, Self::done_scanning).detach();
71
72 ctx.spawn_stream(
73 timer::repeat(Duration::from_millis(100)).map(|_| ()),
74 Self::scanning,
75 |_, _| {},
76 )
77 .detach();
78 }
79
80 tree
81 }
82
83 fn scan_dirs(&self) -> io::Result<()> {
84 let path = self.0.read().path.clone();
85 let metadata = fs::metadata(&path)?;
86 let ino = metadata.ino();
87 let is_symlink = fs::symlink_metadata(&path)?.file_type().is_symlink();
88 let name = path
89 .file_name()
90 .map(|name| OsString::from(name))
91 .unwrap_or(OsString::from("/"));
92 let relative_path = PathBuf::from(&name);
93
94 let mut ignore = IgnoreBuilder::new().build().add_parents(&path).unwrap();
95 if metadata.is_dir() {
96 ignore = ignore.add_child(&path).unwrap();
97 }
98 let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore();
99
100 if metadata.file_type().is_dir() {
101 let is_ignored = is_ignored || name == ".git";
102 let id = self.push_dir(None, name, ino, is_symlink, is_ignored);
103 let (tx, rx) = channel::unbounded();
104
105 let tx_ = tx.clone();
106 tx.send(Ok(DirToScan {
107 id,
108 path,
109 relative_path,
110 ignore: Some(ignore),
111 dirs_to_scan: tx_,
112 }))
113 .unwrap();
114 drop(tx);
115
116 Parallel::<io::Result<()>>::new()
117 .each(0..16, |_| {
118 while let Ok(result) = rx.recv() {
119 self.scan_dir(result?)?;
120 }
121 Ok(())
122 })
123 .run()
124 .into_iter()
125 .collect::<io::Result<()>>()?;
126 } else {
127 self.push_file(None, name, ino, is_symlink, is_ignored, relative_path);
128 }
129
130 Ok(())
131 }
132
133 fn scan_dir(&self, to_scan: DirToScan) -> io::Result<()> {
134 let mut new_children = Vec::new();
135
136 for child_entry in fs::read_dir(&to_scan.path)? {
137 let child_entry = child_entry?;
138 let name = child_entry.file_name();
139 let relative_path = to_scan.relative_path.join(&name);
140 let metadata = child_entry.metadata()?;
141 let ino = metadata.ino();
142 let is_symlink = metadata.file_type().is_symlink();
143
144 if metadata.is_dir() {
145 let path = to_scan.path.join(&name);
146 let mut is_ignored = true;
147 let mut ignore = None;
148
149 if let Some(parent_ignore) = to_scan.ignore.as_ref() {
150 let child_ignore = parent_ignore.add_child(&path).unwrap();
151 is_ignored = child_ignore.matched(&path, true).is_ignore() || name == ".git";
152 if !is_ignored {
153 ignore = Some(child_ignore);
154 }
155 }
156
157 let id = self.push_dir(Some(to_scan.id), name, ino, is_symlink, is_ignored);
158 new_children.push(id);
159
160 let dirs_to_scan = to_scan.dirs_to_scan.clone();
161 let _ = to_scan.dirs_to_scan.send(Ok(DirToScan {
162 id,
163 path,
164 relative_path,
165 ignore,
166 dirs_to_scan,
167 }));
168 } else {
169 let is_ignored = to_scan.ignore.as_ref().map_or(true, |i| {
170 i.matched(to_scan.path.join(&name), false).is_ignore()
171 });
172
173 new_children.push(self.push_file(
174 Some(to_scan.id),
175 name,
176 ino,
177 is_symlink,
178 is_ignored,
179 relative_path,
180 ));
181 };
182 }
183
184 if let Entry::Dir { children, .. } = &mut self.0.write().entries[to_scan.id] {
185 *children = new_children.clone();
186 }
187
188 Ok(())
189 }
190
191 fn push_dir(
192 &self,
193 parent: Option<usize>,
194 name: OsString,
195 ino: u64,
196 is_symlink: bool,
197 is_ignored: bool,
198 ) -> usize {
199 let entries = &mut self.0.write().entries;
200 let dir_id = entries.len();
201 entries.push(Entry::Dir {
202 parent,
203 name,
204 ino,
205 is_symlink,
206 is_ignored,
207 children: Vec::new(),
208 });
209 dir_id
210 }
211
212 fn push_file(
213 &self,
214 parent: Option<usize>,
215 name: OsString,
216 ino: u64,
217 is_symlink: bool,
218 is_ignored: bool,
219 path: PathBuf,
220 ) -> usize {
221 let path = path.to_string_lossy();
222 let lowercase_path = path.to_lowercase().chars().collect::<Vec<_>>();
223 let path = path.chars().collect::<Vec<_>>();
224 let path_chars = CharBag::from(&path[..]);
225
226 let mut state = self.0.write();
227 let entry_id = state.entries.len();
228 state.entries.push(Entry::File {
229 parent,
230 name,
231 ino,
232 is_symlink,
233 is_ignored,
234 });
235 state.file_paths.push(PathEntry {
236 entry_id,
237 path_chars,
238 path,
239 lowercase_path,
240 is_ignored,
241 });
242 entry_id
243 }
244
245 pub fn entry_path(&self, mut entry_id: usize) -> Result<PathBuf> {
246 let state = self.0.read();
247
248 if entry_id >= state.entries.len() {
249 return Err(anyhow!("Entry does not exist in tree"));
250 }
251
252 let mut entries = Vec::new();
253 loop {
254 let entry = &state.entries[entry_id];
255 entries.push(entry);
256 if let Some(parent_id) = entry.parent() {
257 entry_id = parent_id;
258 } else {
259 break;
260 }
261 }
262
263 let mut path = PathBuf::new();
264 for entry in entries.into_iter().rev() {
265 path.push(entry.name());
266 }
267 Ok(path)
268 }
269
270 pub fn abs_entry_path(&self, entry_id: usize) -> Result<PathBuf> {
271 let mut path = self.0.read().path.clone();
272 path.pop();
273 Ok(path.join(self.entry_path(entry_id)?))
274 }
275
276 fn fmt_entry(&self, f: &mut fmt::Formatter<'_>, entry_id: usize, indent: usize) -> fmt::Result {
277 match &self.0.read().entries[entry_id] {
278 Entry::Dir { name, children, .. } => {
279 write!(
280 f,
281 "{}{}/ ({})\n",
282 " ".repeat(indent),
283 name.to_string_lossy(),
284 entry_id
285 )?;
286 for child_id in children.iter() {
287 self.fmt_entry(f, *child_id, indent + 2)?;
288 }
289 Ok(())
290 }
291 Entry::File { name, .. } => write!(
292 f,
293 "{}{} ({})\n",
294 " ".repeat(indent),
295 name.to_string_lossy(),
296 entry_id
297 ),
298 }
299 }
300
301 pub fn path(&self) -> PathBuf {
302 PathBuf::from(&self.0.read().path)
303 }
304
305 pub fn contains_path(&self, path: &Path) -> bool {
306 path.starts_with(self.path())
307 }
308
309 pub fn iter(&self) -> Iter {
310 Iter {
311 tree: self.clone(),
312 stack: Vec::new(),
313 started: false,
314 }
315 }
316
317 pub fn files(&self) -> FilesIter {
318 FilesIter {
319 iter: self.iter(),
320 path: PathBuf::new(),
321 }
322 }
323
324 pub fn entry_count(&self) -> usize {
325 self.0.read().entries.len()
326 }
327
328 pub fn file_count(&self) -> usize {
329 self.0.read().file_paths.len()
330 }
331
332 pub fn load_history(&self, entry_id: usize) -> impl Future<Output = Result<History>> {
333 let tree = self.clone();
334
335 async move {
336 if let Some(history) = tree.0.read().histories.get(&entry_id) {
337 return Ok(history.clone());
338 }
339
340 let path = tree.abs_entry_path(entry_id)?;
341
342 let mut file = smol::fs::File::open(&path).await?;
343 let mut base_text = String::new();
344 file.read_to_string(&mut base_text).await?;
345 let history = History { base_text };
346 tree.0.write().histories.insert(entry_id, history.clone());
347 Ok(history)
348 }
349 }
350
351 fn scanning(&mut self, _: (), ctx: &mut ModelContext<Self>) {
352 if self.0.read().scanning {
353 ctx.notify();
354 } else {
355 ctx.halt_stream();
356 }
357 }
358
359 fn done_scanning(&mut self, result: io::Result<()>, ctx: &mut ModelContext<Self>) {
360 log::info!("done scanning");
361 self.0.write().scanning = false;
362 if let Err(error) = result {
363 log::error!("error populating worktree: {}", error);
364 } else {
365 ctx.notify();
366 }
367 }
368}
369
370impl fmt::Debug for Worktree {
371 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
372 if self.entry_count() == 0 {
373 write!(f, "Empty tree\n")
374 } else {
375 self.fmt_entry(f, 0, 0)
376 }
377 }
378}
379
380impl Entity for Worktree {
381 type Event = ();
382}
383
384pub trait WorktreeHandle {
385 fn file(&self, entry_id: usize, app: &AppContext) -> Result<FileHandle>;
386}
387
388impl WorktreeHandle for ModelHandle<Worktree> {
389 fn file(&self, entry_id: usize, app: &AppContext) -> Result<FileHandle> {
390 if entry_id >= self.as_ref(app).entry_count() {
391 return Err(anyhow!("Entry does not exist in tree"));
392 }
393
394 Ok(FileHandle {
395 worktree: self.clone(),
396 entry_id,
397 })
398 }
399}
400
401#[derive(Clone, Debug)]
402pub enum Entry {
403 Dir {
404 parent: Option<usize>,
405 name: OsString,
406 ino: u64,
407 is_symlink: bool,
408 is_ignored: bool,
409 children: Vec<usize>,
410 },
411 File {
412 parent: Option<usize>,
413 name: OsString,
414 ino: u64,
415 is_symlink: bool,
416 is_ignored: bool,
417 },
418}
419
420impl Entry {
421 fn parent(&self) -> Option<usize> {
422 match self {
423 Entry::Dir { parent, .. } | Entry::File { parent, .. } => *parent,
424 }
425 }
426
427 fn name(&self) -> &OsStr {
428 match self {
429 Entry::Dir { name, .. } | Entry::File { name, .. } => name,
430 }
431 }
432}
433
434#[derive(Clone)]
435pub struct FileHandle {
436 worktree: ModelHandle<Worktree>,
437 entry_id: usize,
438}
439
440impl FileHandle {
441 pub fn path(&self, app: &AppContext) -> PathBuf {
442 self.worktree.as_ref(app).entry_path(self.entry_id).unwrap()
443 }
444
445 pub fn load_history(&self, app: &AppContext) -> impl Future<Output = Result<History>> {
446 self.worktree.as_ref(app).load_history(self.entry_id)
447 }
448
449 pub fn entry_id(&self) -> (usize, usize) {
450 (self.worktree.id(), self.entry_id)
451 }
452}
453
454struct IterStackEntry {
455 entry_id: usize,
456 child_idx: usize,
457}
458
459pub struct Iter {
460 tree: Worktree,
461 stack: Vec<IterStackEntry>,
462 started: bool,
463}
464
465impl Iterator for Iter {
466 type Item = Traversal;
467
468 fn next(&mut self) -> Option<Self::Item> {
469 let state = self.tree.0.read();
470
471 if !self.started {
472 self.started = true;
473
474 return if let Some(entry) = state.entries.first().cloned() {
475 self.stack.push(IterStackEntry {
476 entry_id: 0,
477 child_idx: 0,
478 });
479
480 Some(Traversal::Push { entry_id: 0, entry })
481 } else {
482 None
483 };
484 }
485
486 while let Some(parent) = self.stack.last_mut() {
487 if let Entry::Dir { children, .. } = &state.entries[parent.entry_id] {
488 if parent.child_idx < children.len() {
489 let child_id = children[post_inc(&mut parent.child_idx)];
490
491 self.stack.push(IterStackEntry {
492 entry_id: child_id,
493 child_idx: 0,
494 });
495
496 return Some(Traversal::Push {
497 entry_id: child_id,
498 entry: state.entries[child_id].clone(),
499 });
500 } else {
501 self.stack.pop();
502
503 return Some(Traversal::Pop);
504 }
505 } else {
506 self.stack.pop();
507
508 return Some(Traversal::Pop);
509 }
510 }
511
512 None
513 }
514}
515
516#[derive(Debug)]
517pub enum Traversal {
518 Push { entry_id: usize, entry: Entry },
519 Pop,
520}
521
522pub struct FilesIter {
523 iter: Iter,
524 path: PathBuf,
525}
526
527pub struct FilesIterItem {
528 pub entry_id: usize,
529 pub path: PathBuf,
530}
531
532impl Iterator for FilesIter {
533 type Item = FilesIterItem;
534
535 fn next(&mut self) -> Option<Self::Item> {
536 loop {
537 match self.iter.next() {
538 Some(Traversal::Push {
539 entry_id, entry, ..
540 }) => match entry {
541 Entry::Dir { name, .. } => {
542 self.path.push(name);
543 }
544 Entry::File { name, .. } => {
545 self.path.push(name);
546 return Some(FilesIterItem {
547 entry_id,
548 path: self.path.clone(),
549 });
550 }
551 },
552 Some(Traversal::Pop) => {
553 self.path.pop();
554 }
555 None => {
556 return None;
557 }
558 }
559 }
560 }
561}
562
563trait UnwrapIgnoreTuple {
564 fn unwrap(self) -> Ignore;
565}
566
567impl UnwrapIgnoreTuple for (Ignore, Option<ignore::Error>) {
568 fn unwrap(self) -> Ignore {
569 if let Some(error) = self.1 {
570 log::error!("error loading gitignore data: {}", error);
571 }
572 self.0
573 }
574}
575
576pub fn match_paths(
577 trees: &[Worktree],
578 query: &str,
579 include_ignored: bool,
580 smart_case: bool,
581 max_results: usize,
582) -> Vec<PathMatch> {
583 let tree_states = trees.iter().map(|tree| tree.0.read()).collect::<Vec<_>>();
584 fuzzy::match_paths(
585 &tree_states
586 .iter()
587 .map(|tree| {
588 let skip_prefix = if trees.len() == 1 {
589 if let Some(Entry::Dir { name, .. }) = tree.entries.get(0) {
590 let name = name.to_string_lossy();
591 if name == "/" {
592 1
593 } else {
594 name.chars().count() + 1
595 }
596 } else {
597 0
598 }
599 } else {
600 0
601 };
602
603 (tree.id, skip_prefix, &tree.file_paths[..])
604 })
605 .collect::<Vec<_>>()[..],
606 query,
607 include_ignored,
608 smart_case,
609 max_results,
610 )
611}
612
613// #[cfg(test)]
614// mod test {
615// use super::*;
616// use crate::test_utils::*;
617// use anyhow::Result;
618// use std::os::unix;
619//
620// // #[test]
621// // fn test_populate_and_search() -> Result<()> {
622// // let dir = build_tempdir(json!({
623// // "root": {
624// // "apple": "",
625// // "banana": {
626// // "carrot": {
627// // "date": "",
628// // "endive": "",
629// // }
630// // },
631// // "fennel": {
632// // "grape": "",
633// // }
634// // }
635// // }));
636// //
637// // let root_link_path = dir.path().join("root_link");
638// // unix::fs::symlink(&dir.path().join("root"), &root_link_path)?;
639// //
640// // let tree = Worktree::new(1, root_link_path, None);
641// // let (tx, _) = channel::unbounded();
642// // tree.populate(&tx)?;
643// // assert_eq!(tree.file_count(), 4);
644// //
645// // let results = match_paths(&[tree.clone()], "bna", false, false, 10)
646// // .iter()
647// // .map(|result| tree.entry_path(result.entry_id))
648// // .collect::<Result<Vec<PathBuf>, _>>()?;
649// //
650// // assert_eq!(
651// // results,
652// // vec![
653// // PathBuf::from("root_link/banana/carrot/date"),
654// // PathBuf::from("root_link/banana/carrot/endive"),
655// // ]
656// // );
657// //
658// // Ok(())
659// // }
660// }