diff options
Diffstat (limited to 'crates/ra_vfs/src/lib.rs')
-rw-r--r-- | crates/ra_vfs/src/lib.rs | 146 |
1 files changed, 66 insertions, 80 deletions
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index 70a13f765..71a3f807d 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs | |||
@@ -18,94 +18,78 @@ mod io; | |||
18 | use std::{ | 18 | use std::{ |
19 | cmp::Reverse, | 19 | cmp::Reverse, |
20 | fmt, fs, mem, | 20 | fmt, fs, mem, |
21 | ops::{Deref, DerefMut}, | ||
22 | path::{Path, PathBuf}, | 21 | path::{Path, PathBuf}, |
23 | sync::Arc, | 22 | sync::Arc, |
24 | thread, | 23 | thread, |
25 | }; | 24 | }; |
26 | 25 | ||
27 | use crossbeam_channel::Receiver; | 26 | use crossbeam_channel::Receiver; |
28 | use ra_arena::{impl_arena_id, Arena, RawId}; | 27 | use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap}; |
29 | use relative_path::{Component, RelativePath, RelativePathBuf}; | 28 | use relative_path::{Component, RelativePath, RelativePathBuf}; |
30 | use rustc_hash::{FxHashMap, FxHashSet}; | 29 | use rustc_hash::{FxHashMap, FxHashSet}; |
31 | use walkdir::DirEntry; | ||
32 | 30 | ||
33 | pub use crate::io::TaskResult as VfsTask; | 31 | pub use crate::io::TaskResult as VfsTask; |
34 | use io::{TaskResult, Worker}; | 32 | use io::{TaskResult, Worker}; |
35 | 33 | ||
36 | /// `RootFilter` is a predicate that checks if a file can belong to a root. If | 34 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
37 | /// several filters match a file (nested dirs), the most nested one wins. | 35 | pub struct VfsRoot(pub RawId); |
38 | pub(crate) struct RootFilter { | 36 | impl_arena_id!(VfsRoot); |
37 | |||
38 | /// Describes the contents of a single source root. | ||
39 | /// | ||
40 | /// `RootConfig` can be thought of as a glob pattern like `src/**.rs` whihc | ||
41 | /// specifes the source root or as a function whihc takes a `PathBuf` and | ||
42 | /// returns `true` iff path belongs to the source root | ||
43 | pub(crate) struct RootConfig { | ||
39 | root: PathBuf, | 44 | root: PathBuf, |
40 | filter: fn(&Path, &RelativePath) -> bool, | ||
41 | excluded_dirs: Vec<PathBuf>, | 45 | excluded_dirs: Vec<PathBuf>, |
42 | } | 46 | } |
43 | 47 | ||
44 | impl RootFilter { | 48 | pub(crate) struct Roots { |
45 | fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootFilter { | 49 | roots: Arena<VfsRoot, Arc<RootConfig>>, |
46 | RootFilter { | 50 | } |
51 | |||
52 | impl std::ops::Deref for Roots { | ||
53 | type Target = Arena<VfsRoot, Arc<RootConfig>>; | ||
54 | fn deref(&self) -> &Self::Target { | ||
55 | &self.roots | ||
56 | } | ||
57 | } | ||
58 | |||
59 | impl RootConfig { | ||
60 | fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootConfig { | ||
61 | RootConfig { | ||
47 | root, | 62 | root, |
48 | filter: default_filter, | ||
49 | excluded_dirs, | 63 | excluded_dirs, |
50 | } | 64 | } |
51 | } | 65 | } |
52 | /// Check if this root can contain `path`. NB: even if this returns | 66 | /// Cheks if root contains a path and returns a root-relative path. |
53 | /// true, the `path` might actually be conained in some nested root. | 67 | pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> { |
54 | pub(crate) fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> { | 68 | // First, check excluded dirs |
55 | let rel_path = path.strip_prefix(&self.root).ok()?; | 69 | if self.excluded_dirs.iter().any(|it| path.starts_with(it)) { |
56 | let rel_path = RelativePathBuf::from_path(rel_path).ok()?; | ||
57 | if !(self.filter)(path, rel_path.as_relative_path()) { | ||
58 | return None; | 70 | return None; |
59 | } | 71 | } |
60 | Some(rel_path) | 72 | let rel_path = path.strip_prefix(&self.root).ok()?; |
61 | } | 73 | let rel_path = RelativePathBuf::from_path(rel_path).ok()?; |
62 | |||
63 | pub(crate) fn entry_filter<'a>(&'a self) -> impl FnMut(&DirEntry) -> bool + 'a { | ||
64 | move |entry: &DirEntry| { | ||
65 | if entry.file_type().is_dir() && self.excluded_dirs.iter().any(|it| it == entry.path()) | ||
66 | { | ||
67 | // do not walk nested roots | ||
68 | false | ||
69 | } else { | ||
70 | self.can_contain(entry.path()).is_some() | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | 74 | ||
76 | pub(crate) fn default_filter(path: &Path, rel_path: &RelativePath) -> bool { | 75 | // Ignore some common directories. |
77 | if path.is_dir() { | 76 | // |
77 | // FIXME: don't hard-code, specify at source-root creation time using | ||
78 | // gitignore | ||
78 | for (i, c) in rel_path.components().enumerate() { | 79 | for (i, c) in rel_path.components().enumerate() { |
79 | if let Component::Normal(c) = c { | 80 | if let Component::Normal(c) = c { |
80 | // TODO hardcoded for now | ||
81 | if (i == 0 && c == "target") || c == ".git" || c == "node_modules" { | 81 | if (i == 0 && c == "target") || c == ".git" || c == "node_modules" { |
82 | return false; | 82 | return None; |
83 | } | 83 | } |
84 | } | 84 | } |
85 | } | 85 | } |
86 | true | ||
87 | } else { | ||
88 | rel_path.extension() == Some("rs") | ||
89 | } | ||
90 | } | ||
91 | |||
92 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
93 | pub struct VfsRoot(pub RawId); | ||
94 | impl_arena_id!(VfsRoot); | ||
95 | |||
96 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
97 | pub struct VfsFile(pub RawId); | ||
98 | impl_arena_id!(VfsFile); | ||
99 | 86 | ||
100 | struct VfsFileData { | 87 | if path.is_file() && rel_path.extension() != Some("rs") { |
101 | root: VfsRoot, | 88 | return None; |
102 | path: RelativePathBuf, | 89 | } |
103 | is_overlayed: bool, | ||
104 | text: Arc<String>, | ||
105 | } | ||
106 | 90 | ||
107 | pub(crate) struct Roots { | 91 | Some(rel_path) |
108 | roots: Arena<VfsRoot, Arc<RootFilter>>, | 92 | } |
109 | } | 93 | } |
110 | 94 | ||
111 | impl Roots { | 95 | impl Roots { |
@@ -120,59 +104,61 @@ impl Roots { | |||
120 | .map(|it| it.clone()) | 104 | .map(|it| it.clone()) |
121 | .collect::<Vec<_>>(); | 105 | .collect::<Vec<_>>(); |
122 | 106 | ||
123 | let root_filter = Arc::new(RootFilter::new(path.clone(), nested_roots)); | 107 | let config = Arc::new(RootConfig::new(path.clone(), nested_roots)); |
124 | 108 | ||
125 | roots.alloc(root_filter.clone()); | 109 | roots.alloc(config.clone()); |
126 | } | 110 | } |
127 | Roots { roots } | 111 | Roots { roots } |
128 | } | 112 | } |
129 | pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> { | 113 | pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> { |
130 | self.roots | 114 | self.roots |
131 | .iter() | 115 | .iter() |
132 | .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it))) | 116 | .find_map(|(root, data)| data.contains(path).map(|it| (root, it))) |
133 | } | 117 | } |
134 | } | 118 | } |
135 | 119 | ||
136 | impl Deref for Roots { | 120 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
137 | type Target = Arena<VfsRoot, Arc<RootFilter>>; | 121 | pub struct VfsFile(pub RawId); |
138 | fn deref(&self) -> &Self::Target { | 122 | impl_arena_id!(VfsFile); |
139 | &self.roots | ||
140 | } | ||
141 | } | ||
142 | 123 | ||
143 | impl DerefMut for Roots { | 124 | struct VfsFileData { |
144 | fn deref_mut(&mut self) -> &mut Self::Target { | 125 | root: VfsRoot, |
145 | &mut self.roots | 126 | path: RelativePathBuf, |
146 | } | 127 | is_overlayed: bool, |
128 | text: Arc<String>, | ||
147 | } | 129 | } |
148 | 130 | ||
149 | pub struct Vfs { | 131 | pub struct Vfs { |
150 | roots: Arc<Roots>, | 132 | roots: Arc<Roots>, |
151 | files: Arena<VfsFile, VfsFileData>, | 133 | files: Arena<VfsFile, VfsFileData>, |
152 | root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>, | 134 | root2files: ArenaMap<VfsRoot, FxHashSet<VfsFile>>, |
153 | pending_changes: Vec<VfsChange>, | 135 | pending_changes: Vec<VfsChange>, |
154 | worker: Worker, | 136 | worker: Worker, |
155 | } | 137 | } |
156 | 138 | ||
157 | impl fmt::Debug for Vfs { | 139 | impl fmt::Debug for Vfs { |
158 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | 140 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
159 | f.write_str("Vfs { ... }") | 141 | f.debug_struct("Vfs") |
142 | .field("n_roots", &self.roots.len()) | ||
143 | .field("n_files", &self.files.len()) | ||
144 | .field("n_pending_changes", &self.pending_changes.len()) | ||
145 | .finish() | ||
160 | } | 146 | } |
161 | } | 147 | } |
162 | 148 | ||
163 | impl Vfs { | 149 | impl Vfs { |
164 | pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { | 150 | pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { |
165 | let roots = Arc::new(Roots::new(roots)); | 151 | let roots = Arc::new(Roots::new(roots)); |
166 | let worker = io::Worker::start(roots.clone()); | 152 | let worker = io::Worker::start(Arc::clone(&roots)); |
167 | let mut root2files = FxHashMap::default(); | 153 | let mut root2files = ArenaMap::default(); |
168 | 154 | ||
169 | for (root, filter) in roots.iter() { | 155 | for (root, config) in roots.iter() { |
170 | root2files.insert(root, Default::default()); | 156 | root2files.insert(root, Default::default()); |
171 | worker | 157 | worker |
172 | .sender() | 158 | .sender() |
173 | .send(io::Task::AddRoot { | 159 | .send(io::Task::AddRoot { |
174 | root, | 160 | root, |
175 | filter: filter.clone(), | 161 | config: Arc::clone(config), |
176 | }) | 162 | }) |
177 | .unwrap(); | 163 | .unwrap(); |
178 | } | 164 | } |
@@ -242,7 +228,7 @@ impl Vfs { | |||
242 | let mut cur_files = Vec::new(); | 228 | let mut cur_files = Vec::new(); |
243 | // While we were scanning the root in the backgound, a file might have | 229 | // While we were scanning the root in the backgound, a file might have |
244 | // been open in the editor, so we need to account for that. | 230 | // been open in the editor, so we need to account for that. |
245 | let exising = self.root2files[&root] | 231 | let exising = self.root2files[root] |
246 | .iter() | 232 | .iter() |
247 | .map(|&file| (self.files[file].path.clone(), file)) | 233 | .map(|&file| (self.files[file].path.clone(), file)) |
248 | .collect::<FxHashMap<_, _>>(); | 234 | .collect::<FxHashMap<_, _>>(); |
@@ -384,7 +370,7 @@ impl Vfs { | |||
384 | is_overlayed, | 370 | is_overlayed, |
385 | }; | 371 | }; |
386 | let file = self.files.alloc(data); | 372 | let file = self.files.alloc(data); |
387 | self.root2files.get_mut(&root).unwrap().insert(file); | 373 | self.root2files.get_mut(root).unwrap().insert(file); |
388 | file | 374 | file |
389 | } | 375 | } |
390 | 376 | ||
@@ -399,7 +385,7 @@ impl Vfs { | |||
399 | self.files[file].text = Default::default(); | 385 | self.files[file].text = Default::default(); |
400 | self.files[file].path = Default::default(); | 386 | self.files[file].path = Default::default(); |
401 | let root = self.files[file].root; | 387 | let root = self.files[file].root; |
402 | let removed = self.root2files.get_mut(&root).unwrap().remove(&file); | 388 | let removed = self.root2files.get_mut(root).unwrap().remove(&file); |
403 | assert!(removed); | 389 | assert!(removed); |
404 | } | 390 | } |
405 | 391 | ||
@@ -410,7 +396,7 @@ impl Vfs { | |||
410 | } | 396 | } |
411 | 397 | ||
412 | fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> { | 398 | fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> { |
413 | self.root2files[&root] | 399 | self.root2files[root] |
414 | .iter() | 400 | .iter() |
415 | .map(|&it| it) | 401 | .map(|&it| it) |
416 | .find(|&file| self.files[file].path == path) | 402 | .find(|&file| self.files[file].path == path) |