aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_vfs/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_vfs/src/lib.rs')
-rw-r--r--crates/ra_vfs/src/lib.rs361
1 files changed, 237 insertions, 124 deletions
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs
index cdea18d73..70a13f765 100644
--- a/crates/ra_vfs/src/lib.rs
+++ b/crates/ra_vfs/src/lib.rs
@@ -16,52 +16,77 @@
16mod io; 16mod io;
17 17
18use std::{ 18use std::{
19 fmt,
20 mem,
21 thread,
22 cmp::Reverse, 19 cmp::Reverse,
20 fmt, fs, mem,
21 ops::{Deref, DerefMut},
23 path::{Path, PathBuf}, 22 path::{Path, PathBuf},
24 ffi::OsStr,
25 sync::Arc, 23 sync::Arc,
26 fs, 24 thread,
27}; 25};
28 26
29use rustc_hash::{FxHashMap, FxHashSet};
30use relative_path::RelativePathBuf;
31use crossbeam_channel::Receiver; 27use crossbeam_channel::Receiver;
28use ra_arena::{impl_arena_id, Arena, RawId};
29use relative_path::{Component, RelativePath, RelativePathBuf};
30use rustc_hash::{FxHashMap, FxHashSet};
32use walkdir::DirEntry; 31use walkdir::DirEntry;
33use thread_worker::WorkerHandle;
34use ra_arena::{Arena, RawId, impl_arena_id};
35 32
36pub use crate::io::TaskResult as VfsTask; 33pub use crate::io::TaskResult as VfsTask;
34use io::{TaskResult, Worker};
37 35
38/// `RootFilter` is a predicate that checks if a file can belong to a root. If 36/// `RootFilter` is a predicate that checks if a file can belong to a root. If
39/// several filters match a file (nested dirs), the most nested one wins. 37/// several filters match a file (nested dirs), the most nested one wins.
40struct RootFilter { 38pub(crate) struct RootFilter {
41 root: PathBuf, 39 root: PathBuf,
42 file_filter: fn(&Path) -> bool, 40 filter: fn(&Path, &RelativePath) -> bool,
41 excluded_dirs: Vec<PathBuf>,
43} 42}
44 43
45impl RootFilter { 44impl RootFilter {
46 fn new(root: PathBuf) -> RootFilter { 45 fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootFilter {
47 RootFilter { 46 RootFilter {
48 root, 47 root,
49 file_filter: has_rs_extension, 48 filter: default_filter,
49 excluded_dirs,
50 } 50 }
51 } 51 }
52 /// Check if this root can contain `path`. NB: even if this returns 52 /// Check if this root can contain `path`. NB: even if this returns
53 /// true, the `path` might actually be conained in some nested root. 53 /// true, the `path` might actually be conained in some nested root.
54 fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> { 54 pub(crate) fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
55 if !(self.file_filter)(path) { 55 let rel_path = path.strip_prefix(&self.root).ok()?;
56 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
57 if !(self.filter)(path, rel_path.as_relative_path()) {
56 return None; 58 return None;
57 } 59 }
58 let path = path.strip_prefix(&self.root).ok()?; 60 Some(rel_path)
59 RelativePathBuf::from_path(path).ok() 61 }
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 }
60 } 73 }
61} 74}
62 75
63fn has_rs_extension(p: &Path) -> bool { 76pub(crate) fn default_filter(path: &Path, rel_path: &RelativePath) -> bool {
64 p.extension() == Some(OsStr::new("rs")) 77 if path.is_dir() {
78 for (i, c) in rel_path.components().enumerate() {
79 if let Component::Normal(c) = c {
80 // TODO hardcoded for now
81 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
82 return false;
83 }
84 }
85 }
86 true
87 } else {
88 rel_path.extension() == Some("rs")
89 }
65} 90}
66 91
67#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 92#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -75,16 +100,58 @@ impl_arena_id!(VfsFile);
75struct VfsFileData { 100struct VfsFileData {
76 root: VfsRoot, 101 root: VfsRoot,
77 path: RelativePathBuf, 102 path: RelativePathBuf,
103 is_overlayed: bool,
78 text: Arc<String>, 104 text: Arc<String>,
79} 105}
80 106
107pub(crate) struct Roots {
108 roots: Arena<VfsRoot, Arc<RootFilter>>,
109}
110
111impl Roots {
112 pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
113 let mut roots = Arena::default();
114 // A hack to make nesting work.
115 paths.sort_by_key(|it| Reverse(it.as_os_str().len()));
116 for (i, path) in paths.iter().enumerate() {
117 let nested_roots = paths[..i]
118 .iter()
119 .filter(|it| it.starts_with(path))
120 .map(|it| it.clone())
121 .collect::<Vec<_>>();
122
123 let root_filter = Arc::new(RootFilter::new(path.clone(), nested_roots));
124
125 roots.alloc(root_filter.clone());
126 }
127 Roots { roots }
128 }
129 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
130 self.roots
131 .iter()
132 .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))
133 }
134}
135
136impl Deref for Roots {
137 type Target = Arena<VfsRoot, Arc<RootFilter>>;
138 fn deref(&self) -> &Self::Target {
139 &self.roots
140 }
141}
142
143impl DerefMut for Roots {
144 fn deref_mut(&mut self) -> &mut Self::Target {
145 &mut self.roots
146 }
147}
148
81pub struct Vfs { 149pub struct Vfs {
82 roots: Arena<VfsRoot, RootFilter>, 150 roots: Arc<Roots>,
83 files: Arena<VfsFile, VfsFileData>, 151 files: Arena<VfsFile, VfsFileData>,
84 root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>, 152 root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
85 pending_changes: Vec<VfsChange>, 153 pending_changes: Vec<VfsChange>,
86 worker: io::Worker, 154 worker: Worker,
87 worker_handle: WorkerHandle,
88} 155}
89 156
90impl fmt::Debug for Vfs { 157impl fmt::Debug for Vfs {
@@ -94,44 +161,30 @@ impl fmt::Debug for Vfs {
94} 161}
95 162
96impl Vfs { 163impl Vfs {
97 pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { 164 pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
98 let (worker, worker_handle) = io::start(); 165 let roots = Arc::new(Roots::new(roots));
99 166 let worker = io::Worker::start(roots.clone());
100 let mut res = Vfs { 167 let mut root2files = FxHashMap::default();
101 roots: Arena::default(), 168
169 for (root, filter) in roots.iter() {
170 root2files.insert(root, Default::default());
171 worker
172 .sender()
173 .send(io::Task::AddRoot {
174 root,
175 filter: filter.clone(),
176 })
177 .unwrap();
178 }
179 let res = Vfs {
180 roots,
102 files: Arena::default(), 181 files: Arena::default(),
103 root2files: FxHashMap::default(), 182 root2files,
104 worker, 183 worker,
105 worker_handle,
106 pending_changes: Vec::new(), 184 pending_changes: Vec::new(),
107 }; 185 };
108 186 let vfs_roots = res.roots.iter().map(|(id, _)| id).collect();
109 // A hack to make nesting work. 187 (res, vfs_roots)
110 roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
111 for (i, path) in roots.iter().enumerate() {
112 let root = res.roots.alloc(RootFilter::new(path.clone()));
113 res.root2files.insert(root, Default::default());
114 let nested = roots[..i]
115 .iter()
116 .filter(|it| it.starts_with(path))
117 .map(|it| it.clone())
118 .collect::<Vec<_>>();
119 let filter = move |entry: &DirEntry| {
120 if entry.file_type().is_file() {
121 has_rs_extension(entry.path())
122 } else {
123 nested.iter().all(|it| it != entry.path())
124 }
125 };
126 let task = io::Task {
127 root,
128 path: path.clone(),
129 filter: Box::new(filter),
130 };
131 res.worker.inp.send(task).unwrap();
132 }
133 let roots = res.roots.iter().map(|(id, _)| id).collect();
134 (res, roots)
135 } 188 }
136 189
137 pub fn root2path(&self, root: VfsRoot) -> PathBuf { 190 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
@@ -165,7 +218,7 @@ impl Vfs {
165 } else { 218 } else {
166 let text = fs::read_to_string(path).unwrap_or_default(); 219 let text = fs::read_to_string(path).unwrap_or_default();
167 let text = Arc::new(text); 220 let text = Arc::new(text);
168 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text)); 221 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
169 let change = VfsChange::AddFile { 222 let change = VfsChange::AddFile {
170 file, 223 file,
171 text, 224 text,
@@ -180,85 +233,132 @@ impl Vfs {
180 } 233 }
181 234
182 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> { 235 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
183 &self.worker.out 236 self.worker.receiver()
184 } 237 }
185 238
186 pub fn handle_task(&mut self, task: io::TaskResult) { 239 pub fn handle_task(&mut self, task: io::TaskResult) {
187 let mut files = Vec::new(); 240 match task {
188 // While we were scanning the root in the backgound, a file might have 241 TaskResult::BulkLoadRoot { root, files } => {
189 // been open in the editor, so we need to account for that. 242 let mut cur_files = Vec::new();
190 let exising = self.root2files[&task.root] 243 // While we were scanning the root in the backgound, a file might have
191 .iter() 244 // been open in the editor, so we need to account for that.
192 .map(|&file| (self.files[file].path.clone(), file)) 245 let exising = self.root2files[&root]
193 .collect::<FxHashMap<_, _>>(); 246 .iter()
194 for (path, text) in task.files { 247 .map(|&file| (self.files[file].path.clone(), file))
195 if let Some(&file) = exising.get(&path) { 248 .collect::<FxHashMap<_, _>>();
196 let text = Arc::clone(&self.files[file].text); 249 for (path, text) in files {
197 files.push((file, path, text)); 250 if let Some(&file) = exising.get(&path) {
198 continue; 251 let text = Arc::clone(&self.files[file].text);
252 cur_files.push((file, path, text));
253 continue;
254 }
255 let text = Arc::new(text);
256 let file = self.add_file(root, path.clone(), Arc::clone(&text), false);
257 cur_files.push((file, path, text));
258 }
259
260 let change = VfsChange::AddRoot {
261 root,
262 files: cur_files,
263 };
264 self.pending_changes.push(change);
265 }
266 TaskResult::AddSingleFile { root, path, text } => {
267 if self.find_file(root, &path).is_none() {
268 self.do_add_file(root, path, text, false);
269 }
270 }
271 TaskResult::ChangeSingleFile { root, path, text } => {
272 if let Some(file) = self.find_file(root, &path) {
273 self.do_change_file(file, text, false);
274 } else {
275 self.do_add_file(root, path, text, false);
276 }
277 }
278 TaskResult::RemoveSingleFile { root, path } => {
279 if let Some(file) = self.find_file(root, &path) {
280 self.do_remove_file(root, path, file, false);
281 }
199 } 282 }
200 let text = Arc::new(text);
201 let file = self.add_file(task.root, path.clone(), Arc::clone(&text));
202 files.push((file, path, text));
203 } 283 }
284 }
204 285
205 let change = VfsChange::AddRoot { 286 fn do_add_file(
206 root: task.root, 287 &mut self,
207 files, 288 root: VfsRoot,
208 }; 289 path: RelativePathBuf,
209 self.pending_changes.push(change); 290 text: String,
291 is_overlay: bool,
292 ) -> Option<VfsFile> {
293 let text = Arc::new(text);
294 let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
295 self.pending_changes.push(VfsChange::AddFile {
296 file,
297 root,
298 path,
299 text,
300 });
301 Some(file)
302 }
303
304 fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
305 if !is_overlay && self.files[file].is_overlayed {
306 return;
307 }
308 let text = Arc::new(text);
309 self.change_file(file, text.clone(), is_overlay);
310 self.pending_changes
311 .push(VfsChange::ChangeFile { file, text });
312 }
313
314 fn do_remove_file(
315 &mut self,
316 root: VfsRoot,
317 path: RelativePathBuf,
318 file: VfsFile,
319 is_overlay: bool,
320 ) {
321 if !is_overlay && self.files[file].is_overlayed {
322 return;
323 }
324 self.remove_file(file);
325 self.pending_changes
326 .push(VfsChange::RemoveFile { root, path, file });
210 } 327 }
211 328
212 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> { 329 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
213 let mut res = None; 330 if let Some((root, rel_path, file)) = self.find_root(path) {
214 if let Some((root, path, file)) = self.find_root(path) { 331 if let Some(file) = file {
215 let text = Arc::new(text); 332 self.do_change_file(file, text, true);
216 let change = if let Some(file) = file { 333 Some(file)
217 res = Some(file);
218 self.change_file(file, Arc::clone(&text));
219 VfsChange::ChangeFile { file, text }
220 } else { 334 } else {
221 let file = self.add_file(root, path.clone(), Arc::clone(&text)); 335 self.do_add_file(root, rel_path, text, true)
222 res = Some(file); 336 }
223 VfsChange::AddFile { 337 } else {
224 file, 338 None
225 text,
226 root,
227 path,
228 }
229 };
230 self.pending_changes.push(change);
231 } 339 }
232 res
233 } 340 }
234 341
235 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) { 342 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
236 if let Some((_root, _path, file)) = self.find_root(path) { 343 if let Some((_root, _path, file)) = self.find_root(path) {
237 let file = file.expect("can't change a file which wasn't added"); 344 let file = file.expect("can't change a file which wasn't added");
238 let text = Arc::new(new_text); 345 self.do_change_file(file, new_text, true);
239 self.change_file(file, Arc::clone(&text));
240 let change = VfsChange::ChangeFile { file, text };
241 self.pending_changes.push(change);
242 } 346 }
243 } 347 }
244 348
245 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> { 349 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
246 let mut res = None;
247 if let Some((root, path, file)) = self.find_root(path) { 350 if let Some((root, path, file)) = self.find_root(path) {
248 let file = file.expect("can't remove a file which wasn't added"); 351 let file = file.expect("can't remove a file which wasn't added");
249 res = Some(file);
250 let full_path = path.to_path(&self.roots[root].root); 352 let full_path = path.to_path(&self.roots[root].root);
251 let change = if let Ok(text) = fs::read_to_string(&full_path) { 353 if let Ok(text) = fs::read_to_string(&full_path) {
252 let text = Arc::new(text); 354 self.do_change_file(file, text, true);
253 self.change_file(file, Arc::clone(&text));
254 VfsChange::ChangeFile { file, text }
255 } else { 355 } else {
256 self.remove_file(file); 356 self.do_remove_file(root, path, file, true);
257 VfsChange::RemoveFile { root, file, path } 357 }
258 }; 358 Some(file)
259 self.pending_changes.push(change); 359 } else {
360 None
260 } 361 }
261 res
262 } 362 }
263 363
264 pub fn commit_changes(&mut self) -> Vec<VfsChange> { 364 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
@@ -267,19 +367,31 @@ impl Vfs {
267 367
268 /// Sutdown the VFS and terminate the background watching thread. 368 /// Sutdown the VFS and terminate the background watching thread.
269 pub fn shutdown(self) -> thread::Result<()> { 369 pub fn shutdown(self) -> thread::Result<()> {
270 let _ = self.worker.shutdown(); 370 self.worker.shutdown()
271 self.worker_handle.shutdown()
272 } 371 }
273 372
274 fn add_file(&mut self, root: VfsRoot, path: RelativePathBuf, text: Arc<String>) -> VfsFile { 373 fn add_file(
275 let data = VfsFileData { root, path, text }; 374 &mut self,
375 root: VfsRoot,
376 path: RelativePathBuf,
377 text: Arc<String>,
378 is_overlayed: bool,
379 ) -> VfsFile {
380 let data = VfsFileData {
381 root,
382 path,
383 text,
384 is_overlayed,
385 };
276 let file = self.files.alloc(data); 386 let file = self.files.alloc(data);
277 self.root2files.get_mut(&root).unwrap().insert(file); 387 self.root2files.get_mut(&root).unwrap().insert(file);
278 file 388 file
279 } 389 }
280 390
281 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>) { 391 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
282 self.files[file].text = new_text; 392 let mut file_data = &mut self.files[file];
393 file_data.text = new_text;
394 file_data.is_overlayed = is_overlayed;
283 } 395 }
284 396
285 fn remove_file(&mut self, file: VfsFile) { 397 fn remove_file(&mut self, file: VfsFile) {
@@ -292,15 +404,16 @@ impl Vfs {
292 } 404 }
293 405
294 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> { 406 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
295 let (root, path) = self 407 let (root, path) = self.roots.find(&path)?;
296 .roots 408 let file = self.find_file(root, &path);
297 .iter() 409 Some((root, path, file))
298 .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?; 410 }
299 let file = self.root2files[&root] 411
412 fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
413 self.root2files[&root]
300 .iter() 414 .iter()
301 .map(|&it| it) 415 .map(|&it| it)
302 .find(|&file| self.files[file].path == path); 416 .find(|&file| self.files[file].path == path)
303 Some((root, path, file))
304 } 417 }
305} 418}
306 419