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