aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_vfs/src/roots.rs
blob: 4503458eed173e62513a180a3e96edf607046ee6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use std::{
    iter,
    path::{Path, PathBuf},
};

use relative_path::{ RelativePath, RelativePathBuf};

/// VfsRoot identifies a watched directory on the file system.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VfsRoot(pub u32);

/// Describes the contents of a single source root.
///
/// `RootConfig` can be thought of as a glob pattern like `src/**.rs` which
/// specifies the source root or as a function which takes a `PathBuf` and
/// returns `true` iff path belongs to the source root
struct RootData {
    path: PathBuf,
    // result of `root.canonicalize()` if that differs from `root`; `None` otherwise.
    canonical_path: Option<PathBuf>,
    excluded_dirs: Vec<RelativePathBuf>,
}

pub(crate) struct Roots {
    roots: Vec<RootData>,
}

impl Roots {
    pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
        let mut roots = Vec::new();
        // A hack to make nesting work.
        paths.sort_by_key(|it| std::cmp::Reverse(it.as_os_str().len()));
        paths.dedup();
        for (i, path) in paths.iter().enumerate() {
            let nested_roots =
                paths[..i].iter().filter_map(|it| rel_path(path, it)).collect::<Vec<_>>();

            roots.push(RootData::new(path.clone(), nested_roots));
        }
        Roots { roots }
    }
    pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
        self.iter().find_map(|root| {
            let rel_path = self.contains(root, path)?;
            Some((root, rel_path))
        })
    }
    pub(crate) fn len(&self) -> usize {
        self.roots.len()
    }
    pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = VfsRoot> + 'a {
        (0..self.roots.len()).into_iter().map(|idx| VfsRoot(idx as u32))
    }
    pub(crate) fn path(&self, root: VfsRoot) -> &Path {
        self.root(root).path.as_path()
    }
    /// Checks if root contains a path and returns a root-relative path.
    pub(crate) fn contains(&self, root: VfsRoot, path: &Path) -> Option<RelativePathBuf> {
        let data = self.root(root);
        iter::once(&data.path)
            .chain(data.canonical_path.as_ref().into_iter())
            .find_map(|base| rel_path(base, path))
            .filter(|path| !data.excluded_dirs.contains(path))
            .filter(|path| !data.is_excluded(path))
    }

    fn root(&self, root: VfsRoot) -> &RootData {
        &self.roots[root.0 as usize]
    }
}

impl RootData {
    fn new(path: PathBuf, excluded_dirs: Vec<RelativePathBuf>) -> RootData {
        let mut canonical_path = path.canonicalize().ok();
        if Some(&path) == canonical_path.as_ref() {
            canonical_path = None;
        }
        RootData { path, canonical_path, excluded_dirs }
    }

    fn is_excluded(&self, path: &RelativePath) -> bool {
        if self.excluded_dirs.iter().any(|it| it == path) {
            return true;
        }
        // Ignore some common directories.
        //
        // FIXME: don't hard-code, specify at source-root creation time using
        // gitignore
        for (i, c) in path.components().enumerate() {
            if let relative_path::Component::Normal(c) = c {
                if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
                    return true;
                }
            }
        }

        match path.extension() {
            None | Some("rs") => false,
            _ => true,
        }
    }
}

fn rel_path(base: &Path, path: &Path) -> Option<RelativePathBuf> {
    let path = path.strip_prefix(base).ok()?;
    let path = RelativePathBuf::from_path(path).unwrap();
    Some(path)
}