worktree.rs

  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// }