worktree.rs

  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}