aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_batch/src/lib.rs8
-rw-r--r--crates/ra_vfs/src/io.rs31
-rw-r--r--crates/ra_vfs/src/lib.rs248
-rw-r--r--crates/ra_vfs/src/roots.rs109
4 files changed, 201 insertions, 195 deletions
diff --git a/crates/ra_batch/src/lib.rs b/crates/ra_batch/src/lib.rs
index 69d66113e..89a234f4c 100644
--- a/crates/ra_batch/src/lib.rs
+++ b/crates/ra_batch/src/lib.rs
@@ -75,7 +75,7 @@ impl BatchDatabase {
75 let source_root = SourceRoot { files: file_map }; 75 let source_root = SourceRoot { files: file_map };
76 db.set_source_root(source_root_id, Arc::new(source_root)); 76 db.set_source_root(source_root_id, Arc::new(source_root));
77 roots_loaded.insert(source_root_id); 77 roots_loaded.insert(source_root_id);
78 if roots_loaded.len() == vfs.num_roots() { 78 if roots_loaded.len() == vfs.n_roots() {
79 done = true; 79 done = true;
80 } 80 }
81 } 81 }
@@ -137,14 +137,14 @@ mod tests {
137 path = path.parent().unwrap().to_owned(); 137 path = path.parent().unwrap().to_owned();
138 } 138 }
139 let (db, roots) = BatchDatabase::load_cargo(path).unwrap(); 139 let (db, roots) = BatchDatabase::load_cargo(path).unwrap();
140 let mut num_crates = 0; 140 let mut n_crates = 0;
141 for root in roots { 141 for root in roots {
142 for _krate in Crate::source_root_crates(&db, root) { 142 for _krate in Crate::source_root_crates(&db, root) {
143 num_crates += 1; 143 n_crates += 1;
144 } 144 }
145 } 145 }
146 146
147 // RA has quite a few crates, but the exact count doesn't matter 147 // RA has quite a few crates, but the exact count doesn't matter
148 assert!(num_crates > 20); 148 assert!(n_crates > 20);
149 } 149 }
150} 150}
diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs
index f64b4c532..0cffc03f3 100644
--- a/crates/ra_vfs/src/io.rs
+++ b/crates/ra_vfs/src/io.rs
@@ -9,10 +9,10 @@ use relative_path::RelativePathBuf;
9use walkdir::WalkDir; 9use walkdir::WalkDir;
10use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; 10use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
11 11
12use crate::{RootConfig, Roots, VfsRoot}; 12use crate::{Roots, VfsRoot};
13 13
14pub(crate) enum Task { 14pub(crate) enum Task {
15 AddRoot { root: VfsRoot, config: Arc<RootConfig> }, 15 AddRoot { root: VfsRoot },
16} 16}
17 17
18/// `TaskResult` transfers files read on the IO thread to the VFS on the main 18/// `TaskResult` transfers files read on the IO thread to the VFS on the main
@@ -98,8 +98,8 @@ pub(crate) fn start(roots: Arc<Roots>) -> Worker {
98 drop(input_receiver); 98 drop(input_receiver);
99 break 99 break
100 }, 100 },
101 Ok(Task::AddRoot { root, config }) => { 101 Ok(Task::AddRoot { root }) => {
102 watch_root(watcher.as_mut(), &output_sender, root, Arc::clone(&config)); 102 watch_root(watcher.as_mut(), &output_sender, &*roots, root);
103 } 103 }
104 }, 104 },
105 // Watcher send us changes. If **this** channel is 105 // Watcher send us changes. If **this** channel is
@@ -123,20 +123,21 @@ pub(crate) fn start(roots: Arc<Roots>) -> Worker {
123fn watch_root( 123fn watch_root(
124 watcher: Option<&mut RecommendedWatcher>, 124 watcher: Option<&mut RecommendedWatcher>,
125 sender: &Sender<TaskResult>, 125 sender: &Sender<TaskResult>,
126 roots: &Roots,
126 root: VfsRoot, 127 root: VfsRoot,
127 config: Arc<RootConfig>,
128) { 128) {
129 log::debug!("loading {} ...", config.root.as_path().display()); 129 let root_path = roots.path(root);
130 let files = watch_recursive(watcher, config.root.as_path(), &*config) 130 log::debug!("loading {} ...", root_path.display());
131 let files = watch_recursive(watcher, root_path, roots, root)
131 .into_iter() 132 .into_iter()
132 .filter_map(|path| { 133 .filter_map(|path| {
133 let abs_path = path.to_path(&config.root); 134 let abs_path = path.to_path(&root_path);
134 let text = read_to_string(&abs_path)?; 135 let text = read_to_string(&abs_path)?;
135 Some((path, text)) 136 Some((path, text))
136 }) 137 })
137 .collect(); 138 .collect();
138 sender.send(TaskResult::BulkLoadRoot { root, files }).unwrap(); 139 sender.send(TaskResult::BulkLoadRoot { root, files }).unwrap();
139 log::debug!("... loaded {}", config.root.as_path().display()); 140 log::debug!("... loaded {}", root_path.display());
140} 141}
141 142
142fn convert_notify_event(event: DebouncedEvent, sender: &Sender<(PathBuf, ChangeKind)>) { 143fn convert_notify_event(event: DebouncedEvent, sender: &Sender<(PathBuf, ChangeKind)>) {
@@ -181,19 +182,18 @@ fn handle_change(
181 None => return, 182 None => return,
182 Some(it) => it, 183 Some(it) => it,
183 }; 184 };
184 let config = &roots[root];
185 match kind { 185 match kind {
186 ChangeKind::Create => { 186 ChangeKind::Create => {
187 let mut paths = Vec::new(); 187 let mut paths = Vec::new();
188 if path.is_dir() { 188 if path.is_dir() {
189 paths.extend(watch_recursive(watcher, &path, &config)); 189 paths.extend(watch_recursive(watcher, &path, roots, root));
190 } else { 190 } else {
191 paths.push(rel_path); 191 paths.push(rel_path);
192 } 192 }
193 paths 193 paths
194 .into_iter() 194 .into_iter()
195 .try_for_each(|rel_path| { 195 .try_for_each(|rel_path| {
196 let abs_path = rel_path.to_path(&config.root); 196 let abs_path = rel_path.to_path(&roots.path(root));
197 let text = read_to_string(&abs_path); 197 let text = read_to_string(&abs_path);
198 sender.send(TaskResult::SingleFile { root, path: rel_path, text }) 198 sender.send(TaskResult::SingleFile { root, path: rel_path, text })
199 }) 199 })
@@ -209,12 +209,13 @@ fn handle_change(
209fn watch_recursive( 209fn watch_recursive(
210 mut watcher: Option<&mut RecommendedWatcher>, 210 mut watcher: Option<&mut RecommendedWatcher>,
211 dir: &Path, 211 dir: &Path,
212 config: &RootConfig, 212 roots: &Roots,
213 root: VfsRoot,
213) -> Vec<RelativePathBuf> { 214) -> Vec<RelativePathBuf> {
214 let mut files = Vec::new(); 215 let mut files = Vec::new();
215 for entry in WalkDir::new(dir) 216 for entry in WalkDir::new(dir)
216 .into_iter() 217 .into_iter()
217 .filter_entry(|it| config.contains(it.path()).is_some()) 218 .filter_entry(|it| roots.contains(root, it.path()).is_some())
218 .filter_map(|it| it.map_err(|e| log::warn!("watcher error: {}", e)).ok()) 219 .filter_map(|it| it.map_err(|e| log::warn!("watcher error: {}", e)).ok())
219 { 220 {
220 if entry.file_type().is_dir() { 221 if entry.file_type().is_dir() {
@@ -222,7 +223,7 @@ fn watch_recursive(
222 watch_one(watcher, entry.path()); 223 watch_one(watcher, entry.path());
223 } 224 }
224 } else { 225 } else {
225 let path = config.contains(entry.path()).unwrap(); 226 let path = roots.contains(root, entry.path()).unwrap();
226 files.push(path.to_owned()); 227 files.push(path.to_owned());
227 } 228 }
228 } 229 }
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs
index cfdc1275f..7f555a3c0 100644
--- a/crates/ra_vfs/src/lib.rs
+++ b/crates/ra_vfs/src/lib.rs
@@ -15,10 +15,10 @@
15//! VFS is based on a concept of roots: a set of directories on the file system 15//! VFS is based on a concept of roots: a set of directories on the file system
16//! which are watched for changes. Typically, there will be a root for each 16//! which are watched for changes. Typically, there will be a root for each
17//! Cargo package. 17//! Cargo package.
18mod roots;
18mod io; 19mod io;
19 20
20use std::{ 21use std::{
21 cmp::Reverse,
22 fmt, fs, mem, 22 fmt, fs, mem,
23 path::{Path, PathBuf}, 23 path::{Path, PathBuf},
24 sync::Arc, 24 sync::Arc,
@@ -26,106 +26,18 @@ use std::{
26 26
27use crossbeam_channel::Receiver; 27use crossbeam_channel::Receiver;
28use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap}; 28use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap};
29use relative_path::{Component, RelativePath, RelativePathBuf}; 29use relative_path::{RelativePath, RelativePathBuf};
30use rustc_hash::{FxHashMap, FxHashSet}; 30use rustc_hash::{FxHashMap, FxHashSet};
31 31
32pub use crate::io::TaskResult as VfsTask; 32use crate::{
33use io::{TaskResult, Worker}; 33 io::{TaskResult, Worker},
34 34 roots::Roots,
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 35};
36pub struct VfsRoot(pub RawId);
37impl_arena_id!(VfsRoot);
38
39/// Describes the contents of a single source root.
40///
41/// `RootConfig` can be thought of as a glob pattern like `src/**.rs` which
42/// specifies the source root or as a function which takes a `PathBuf` and
43/// returns `true` iff path belongs to the source root
44pub(crate) struct RootConfig {
45 root: PathBuf,
46 // result of `root.canonicalize()` if that differs from `root`; `None` otherwise.
47 canonical_root: Option<PathBuf>,
48 excluded_dirs: Vec<PathBuf>,
49}
50
51pub(crate) struct Roots {
52 roots: Arena<VfsRoot, Arc<RootConfig>>,
53}
54
55impl std::ops::Deref for Roots {
56 type Target = Arena<VfsRoot, Arc<RootConfig>>;
57 fn deref(&self) -> &Self::Target {
58 &self.roots
59 }
60}
61
62impl RootConfig {
63 fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootConfig {
64 let mut canonical_root = root.canonicalize().ok();
65 if Some(&root) == canonical_root.as_ref() {
66 canonical_root = None;
67 }
68 RootConfig { root, canonical_root, excluded_dirs }
69 }
70 /// Checks if root contains a path and returns a root-relative path.
71 pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> {
72 // First, check excluded dirs
73 if self.excluded_dirs.iter().any(|it| path.starts_with(it)) {
74 return None;
75 }
76 let rel_path = path
77 .strip_prefix(&self.root)
78 .or_else(|err_payload| {
79 self.canonical_root
80 .as_ref()
81 .map_or(Err(err_payload), |canonical_root| path.strip_prefix(canonical_root))
82 })
83 .ok()?;
84 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
85
86 // Ignore some common directories.
87 //
88 // FIXME: don't hard-code, specify at source-root creation time using
89 // gitignore
90 for (i, c) in rel_path.components().enumerate() {
91 if let Component::Normal(c) = c {
92 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
93 return None;
94 }
95 }
96 }
97
98 if path.is_file() && rel_path.extension() != Some("rs") {
99 return None;
100 }
101
102 Some(rel_path)
103 }
104}
105 36
106impl Roots { 37pub use crate::{
107 pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots { 38 io::TaskResult as VfsTask,
108 let mut roots = Arena::default(); 39 roots::VfsRoot,
109 // A hack to make nesting work. 40};
110 paths.sort_by_key(|it| Reverse(it.as_os_str().len()));
111 paths.dedup();
112 for (i, path) in paths.iter().enumerate() {
113 let nested_roots = paths[..i]
114 .iter()
115 .filter(|it| it.starts_with(path))
116 .map(|it| it.clone())
117 .collect::<Vec<_>>();
118
119 let config = Arc::new(RootConfig::new(path.clone(), nested_roots));
120
121 roots.alloc(config.clone());
122 }
123 Roots { roots }
124 }
125 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
126 self.roots.iter().find_map(|(root, data)| data.contains(path).map(|it| (root, it)))
127 }
128}
129 41
130#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
131pub struct VfsFile(pub RawId); 43pub struct VfsFile(pub RawId);
@@ -162,18 +74,18 @@ impl Vfs {
162 let worker = io::start(Arc::clone(&roots)); 74 let worker = io::start(Arc::clone(&roots));
163 let mut root2files = ArenaMap::default(); 75 let mut root2files = ArenaMap::default();
164 76
165 for (root, config) in roots.iter() { 77 for root in roots.iter() {
166 root2files.insert(root, Default::default()); 78 root2files.insert(root, Default::default());
167 worker.sender().send(io::Task::AddRoot { root, config: Arc::clone(config) }).unwrap(); 79 worker.sender().send(io::Task::AddRoot { root }).unwrap();
168 } 80 }
169 let res = 81 let res =
170 Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() }; 82 Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() };
171 let vfs_roots = res.roots.iter().map(|(id, _)| id).collect(); 83 let vfs_roots = res.roots.iter().collect();
172 (res, vfs_roots) 84 (res, vfs_roots)
173 } 85 }
174 86
175 pub fn root2path(&self, root: VfsRoot) -> PathBuf { 87 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
176 self.roots[root].root.clone() 88 self.roots.path(root).to_path_buf()
177 } 89 }
178 90
179 pub fn path2file(&self, path: &Path) -> Option<VfsFile> { 91 pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
@@ -185,18 +97,11 @@ impl Vfs {
185 97
186 pub fn file2path(&self, file: VfsFile) -> PathBuf { 98 pub fn file2path(&self, file: VfsFile) -> PathBuf {
187 let rel_path = &self.files[file].path; 99 let rel_path = &self.files[file].path;
188 let root_path = &self.roots[self.files[file].root].root; 100 let root_path = &self.roots.path(self.files[file].root);
189 rel_path.to_path(root_path) 101 rel_path.to_path(root_path)
190 } 102 }
191 103
192 pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> { 104 pub fn n_roots(&self) -> usize {
193 if let Some((_root, _path, Some(file))) = self.find_root(path) {
194 return Some(file);
195 }
196 None
197 }
198
199 pub fn num_roots(&self) -> usize {
200 self.roots.len() 105 self.roots.len()
201 } 106 }
202 107
@@ -207,7 +112,7 @@ impl Vfs {
207 } else { 112 } else {
208 let text = fs::read_to_string(path).unwrap_or_default(); 113 let text = fs::read_to_string(path).unwrap_or_default();
209 let text = Arc::new(text); 114 let text = Arc::new(text);
210 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false); 115 let file = self.raw_add_file(root, rel_path.clone(), Arc::clone(&text), false);
211 let change = VfsChange::AddFile { file, text, root, path: rel_path }; 116 let change = VfsChange::AddFile { file, text, root, path: rel_path };
212 self.pending_changes.push(change); 117 self.pending_changes.push(change);
213 Some(file) 118 Some(file)
@@ -216,6 +121,39 @@ impl Vfs {
216 None 121 None
217 } 122 }
218 123
124 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
125 let (root, rel_path, file) = self.find_root(path)?;
126 if let Some(file) = file {
127 self.change_file_event(file, text, true);
128 Some(file)
129 } else {
130 self.add_file_event(root, rel_path, text, true)
131 }
132 }
133
134 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
135 if let Some((_root, _path, file)) = self.find_root(path) {
136 let file = file.expect("can't change a file which wasn't added");
137 self.change_file_event(file, new_text, true);
138 }
139 }
140
141 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
142 let (root, rel_path, file) = self.find_root(path)?;
143 let file = file.expect("can't remove a file which wasn't added");
144 let full_path = rel_path.to_path(&self.roots.path(root));
145 if let Ok(text) = fs::read_to_string(&full_path) {
146 self.change_file_event(file, text, false);
147 } else {
148 self.remove_file_event(root, rel_path, file);
149 }
150 Some(file)
151 }
152
153 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
154 mem::replace(&mut self.pending_changes, Vec::new())
155 }
156
219 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> { 157 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
220 self.worker.receiver() 158 self.worker.receiver()
221 } 159 }
@@ -237,7 +175,7 @@ impl Vfs {
237 continue; 175 continue;
238 } 176 }
239 let text = Arc::new(text); 177 let text = Arc::new(text);
240 let file = self.add_file(root, path.clone(), Arc::clone(&text), false); 178 let file = self.raw_add_file(root, path.clone(), Arc::clone(&text), false);
241 cur_files.push((file, path, text)); 179 cur_files.push((file, path, text));
242 } 180 }
243 181
@@ -245,15 +183,19 @@ impl Vfs {
245 self.pending_changes.push(change); 183 self.pending_changes.push(change);
246 } 184 }
247 TaskResult::SingleFile { root, path, text } => { 185 TaskResult::SingleFile { root, path, text } => {
248 match (self.find_file(root, &path), text) { 186 let existing_file = self.find_file(root, &path);
187 if existing_file.map(|file| self.files[file].is_overlayed) == Some(true) {
188 return;
189 }
190 match (existing_file, text) {
249 (Some(file), None) => { 191 (Some(file), None) => {
250 self.do_remove_file(root, path, file, false); 192 self.remove_file_event(root, path, file);
251 } 193 }
252 (None, Some(text)) => { 194 (None, Some(text)) => {
253 self.do_add_file(root, path, text, false); 195 self.add_file_event(root, path, text, false);
254 } 196 }
255 (Some(file), Some(text)) => { 197 (Some(file), Some(text)) => {
256 self.do_change_file(file, text, false); 198 self.change_file_event(file, text, false);
257 } 199 }
258 (None, None) => (), 200 (None, None) => (),
259 } 201 }
@@ -261,7 +203,10 @@ impl Vfs {
261 } 203 }
262 } 204 }
263 205
264 fn do_add_file( 206 // *_event calls change the state of VFS and push a change onto pending
207 // changes array.
208
209 fn add_file_event(
265 &mut self, 210 &mut self,
266 root: VfsRoot, 211 root: VfsRoot,
267 path: RelativePathBuf, 212 path: RelativePathBuf,
@@ -269,74 +214,25 @@ impl Vfs {
269 is_overlay: bool, 214 is_overlay: bool,
270 ) -> Option<VfsFile> { 215 ) -> Option<VfsFile> {
271 let text = Arc::new(text); 216 let text = Arc::new(text);
272 let file = self.add_file(root, path.clone(), text.clone(), is_overlay); 217 let file = self.raw_add_file(root, path.clone(), text.clone(), is_overlay);
273 self.pending_changes.push(VfsChange::AddFile { file, root, path, text }); 218 self.pending_changes.push(VfsChange::AddFile { file, root, path, text });
274 Some(file) 219 Some(file)
275 } 220 }
276 221
277 fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) { 222 fn change_file_event(&mut self, file: VfsFile, text: String, is_overlay: bool) {
278 if !is_overlay && self.files[file].is_overlayed {
279 return;
280 }
281 let text = Arc::new(text); 223 let text = Arc::new(text);
282 self.change_file(file, text.clone(), is_overlay); 224 self.raw_change_file(file, text.clone(), is_overlay);
283 self.pending_changes.push(VfsChange::ChangeFile { file, text }); 225 self.pending_changes.push(VfsChange::ChangeFile { file, text });
284 } 226 }
285 227
286 fn do_remove_file( 228 fn remove_file_event(&mut self, root: VfsRoot, path: RelativePathBuf, file: VfsFile) {
287 &mut self, 229 self.raw_remove_file(file);
288 root: VfsRoot,
289 path: RelativePathBuf,
290 file: VfsFile,
291 is_overlay: bool,
292 ) {
293 if !is_overlay && self.files[file].is_overlayed {
294 return;
295 }
296 self.remove_file(file);
297 self.pending_changes.push(VfsChange::RemoveFile { root, path, file }); 230 self.pending_changes.push(VfsChange::RemoveFile { root, path, file });
298 } 231 }
299 232
300 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> { 233 // raw_* calls change the state of VFS, but **do not** emit events.
301 if let Some((root, rel_path, file)) = self.find_root(path) {
302 if let Some(file) = file {
303 self.do_change_file(file, text, true);
304 Some(file)
305 } else {
306 self.do_add_file(root, rel_path, text, true)
307 }
308 } else {
309 None
310 }
311 }
312
313 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
314 if let Some((_root, _path, file)) = self.find_root(path) {
315 let file = file.expect("can't change a file which wasn't added");
316 self.do_change_file(file, new_text, true);
317 }
318 }
319
320 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
321 if let Some((root, path, file)) = self.find_root(path) {
322 let file = file.expect("can't remove a file which wasn't added");
323 let full_path = path.to_path(&self.roots[root].root);
324 if let Ok(text) = fs::read_to_string(&full_path) {
325 self.do_change_file(file, text, true);
326 } else {
327 self.do_remove_file(root, path, file, true);
328 }
329 Some(file)
330 } else {
331 None
332 }
333 }
334
335 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
336 mem::replace(&mut self.pending_changes, Vec::new())
337 }
338 234
339 fn add_file( 235 fn raw_add_file(
340 &mut self, 236 &mut self,
341 root: VfsRoot, 237 root: VfsRoot,
342 path: RelativePathBuf, 238 path: RelativePathBuf,
@@ -349,13 +245,13 @@ impl Vfs {
349 file 245 file
350 } 246 }
351 247
352 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) { 248 fn raw_change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
353 let mut file_data = &mut self.files[file]; 249 let mut file_data = &mut self.files[file];
354 file_data.text = new_text; 250 file_data.text = new_text;
355 file_data.is_overlayed = is_overlayed; 251 file_data.is_overlayed = is_overlayed;
356 } 252 }
357 253
358 fn remove_file(&mut self, file: VfsFile) { 254 fn raw_remove_file(&mut self, file: VfsFile) {
359 // FIXME: use arena with removal 255 // FIXME: use arena with removal
360 self.files[file].text = Default::default(); 256 self.files[file].text = Default::default();
361 self.files[file].path = Default::default(); 257 self.files[file].path = Default::default();
diff --git a/crates/ra_vfs/src/roots.rs b/crates/ra_vfs/src/roots.rs
new file mode 100644
index 000000000..5e2776a35
--- /dev/null
+++ b/crates/ra_vfs/src/roots.rs
@@ -0,0 +1,109 @@
1use std::{
2 iter,
3 sync::Arc,
4 path::{Path, PathBuf},
5};
6
7use relative_path::{ RelativePath, RelativePathBuf};
8use ra_arena::{impl_arena_id, Arena, RawId};
9
10/// VfsRoot identifies a watched directory on the file system.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct VfsRoot(pub RawId);
13impl_arena_id!(VfsRoot);
14
15/// Describes the contents of a single source root.
16///
17/// `RootConfig` can be thought of as a glob pattern like `src/**.rs` which
18/// specifies the source root or as a function which takes a `PathBuf` and
19/// returns `true` iff path belongs to the source root
20struct RootData {
21 path: PathBuf,
22 // result of `root.canonicalize()` if that differs from `root`; `None` otherwise.
23 canonical_path: Option<PathBuf>,
24 excluded_dirs: Vec<RelativePathBuf>,
25}
26
27pub(crate) struct Roots {
28 roots: Arena<VfsRoot, Arc<RootData>>,
29}
30
31impl Roots {
32 pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
33 let mut roots = Arena::default();
34 // A hack to make nesting work.
35 paths.sort_by_key(|it| std::cmp::Reverse(it.as_os_str().len()));
36 paths.dedup();
37 for (i, path) in paths.iter().enumerate() {
38 let nested_roots =
39 paths[..i].iter().filter_map(|it| rel_path(path, it)).collect::<Vec<_>>();
40
41 let config = Arc::new(RootData::new(path.clone(), nested_roots));
42
43 roots.alloc(config.clone());
44 }
45 Roots { roots }
46 }
47 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
48 self.iter().find_map(|root| {
49 let rel_path = self.contains(root, path)?;
50 Some((root, rel_path))
51 })
52 }
53 pub(crate) fn len(&self) -> usize {
54 self.roots.len()
55 }
56 pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = VfsRoot> + 'a {
57 self.roots.iter().map(|(id, _)| id)
58 }
59 pub(crate) fn path(&self, root: VfsRoot) -> &Path {
60 self.roots[root].path.as_path()
61 }
62 /// Checks if root contains a path and returns a root-relative path.
63 pub(crate) fn contains(&self, root: VfsRoot, path: &Path) -> Option<RelativePathBuf> {
64 let data = &self.roots[root];
65 iter::once(&data.path)
66 .chain(data.canonical_path.as_ref().into_iter())
67 .find_map(|base| rel_path(base, path))
68 .filter(|path| !data.excluded_dirs.contains(path))
69 .filter(|path| !data.is_excluded(path))
70 }
71}
72
73impl RootData {
74 fn new(path: PathBuf, excluded_dirs: Vec<RelativePathBuf>) -> RootData {
75 let mut canonical_path = path.canonicalize().ok();
76 if Some(&path) == canonical_path.as_ref() {
77 canonical_path = None;
78 }
79 RootData { path, canonical_path, excluded_dirs }
80 }
81
82 fn is_excluded(&self, path: &RelativePath) -> bool {
83 if self.excluded_dirs.iter().any(|it| it == path) {
84 return true;
85 }
86 // Ignore some common directories.
87 //
88 // FIXME: don't hard-code, specify at source-root creation time using
89 // gitignore
90 for (i, c) in path.components().enumerate() {
91 if let relative_path::Component::Normal(c) = c {
92 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
93 return true;
94 }
95 }
96 }
97
98 match path.extension() {
99 None | Some("rs") => false,
100 _ => true,
101 }
102 }
103}
104
105fn rel_path(base: &Path, path: &Path) -> Option<RelativePathBuf> {
106 let path = path.strip_prefix(base).ok()?;
107 let path = RelativePathBuf::from_path(path).unwrap();
108 Some(path)
109}