]> git.lizzy.rs Git - rust.git/blob - crates/ra_vfs/src/lib.rs
use released `notifiy` version, add TODO comment
[rust.git] / crates / ra_vfs / src / lib.rs
1 //! VFS stands for Virtual File System.
2 //!
3 //! When doing analysis, we don't want to do any IO, we want to keep all source
4 //! code in memory. However, the actual source code is stored on disk, so you
5 //! need to get it into the memory in the first place somehow. VFS is the
6 //! component which does this.
7 //!
8 //! It is also responsible for watching the disk for changes, and for merging
9 //! editor state (modified, unsaved files) with disk state.
10 //! TODO: Some LSP clients support watching the disk, so this crate should
11 //! to support custom watcher events (related to https://github.com/rust-analyzer/rust-analyzer/issues/131)
12 //!
13 //! VFS is based on a concept of roots: a set of directories on the file system
14 //! which are watched for changes. Typically, there will be a root for each
15 //! Cargo package.
16 mod io;
17
18 use std::{
19     cmp::Reverse,
20     fmt, fs, mem,
21     path::{Path, PathBuf},
22     sync::Arc,
23     thread,
24 };
25
26 use crossbeam_channel::Receiver;
27 use ra_arena::{impl_arena_id, Arena, RawId};
28 use relative_path::{Component, RelativePath, RelativePathBuf};
29 use rustc_hash::{FxHashMap, FxHashSet};
30
31 pub use crate::io::TaskResult as VfsTask;
32 use io::{Task, TaskResult, WatcherChange, WatcherChangeData, Worker};
33
34 /// `RootFilter` is a predicate that checks if a file can belong to a root. If
35 /// several filters match a file (nested dirs), the most nested one wins.
36 pub(crate) struct RootFilter {
37     root: PathBuf,
38     filter: fn(&Path, &RelativePath) -> bool,
39 }
40
41 impl RootFilter {
42     fn new(root: PathBuf) -> RootFilter {
43         RootFilter {
44             root,
45             filter: default_filter,
46         }
47     }
48     /// Check if this root can contain `path`. NB: even if this returns
49     /// true, the `path` might actually be conained in some nested root.
50     pub(crate) fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
51         let rel_path = path.strip_prefix(&self.root).ok()?;
52         let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
53         if !(self.filter)(path, rel_path.as_relative_path()) {
54             return None;
55         }
56         Some(rel_path)
57     }
58 }
59
60 pub(crate) fn default_filter(path: &Path, rel_path: &RelativePath) -> bool {
61     if path.is_dir() {
62         for (i, c) in rel_path.components().enumerate() {
63             if let Component::Normal(c) = c {
64                 // TODO hardcoded for now
65                 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
66                     return false;
67                 }
68             }
69         }
70         true
71     } else {
72         rel_path.extension() == Some("rs")
73     }
74 }
75
76 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
77 pub struct VfsRoot(pub RawId);
78 impl_arena_id!(VfsRoot);
79
80 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
81 pub struct VfsFile(pub RawId);
82 impl_arena_id!(VfsFile);
83
84 struct VfsFileData {
85     root: VfsRoot,
86     path: RelativePathBuf,
87     is_overlayed: bool,
88     text: Arc<String>,
89 }
90
91 pub struct Vfs {
92     roots: Arena<VfsRoot, Arc<RootFilter>>,
93     files: Arena<VfsFile, VfsFileData>,
94     root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
95     pending_changes: Vec<VfsChange>,
96     worker: Worker,
97 }
98
99 impl fmt::Debug for Vfs {
100     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101         f.write_str("Vfs { ... }")
102     }
103 }
104
105 impl Vfs {
106     pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
107         let worker = io::Worker::start();
108
109         let mut res = Vfs {
110             roots: Arena::default(),
111             files: Arena::default(),
112             root2files: FxHashMap::default(),
113             worker,
114             pending_changes: Vec::new(),
115         };
116
117         // A hack to make nesting work.
118         roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
119         for (i, path) in roots.iter().enumerate() {
120             let root_filter = Arc::new(RootFilter::new(path.clone()));
121
122             let root = res.roots.alloc(root_filter.clone());
123             res.root2files.insert(root, Default::default());
124
125             let nested_roots = roots[..i]
126                 .iter()
127                 .filter(|it| it.starts_with(path))
128                 .map(|it| it.clone())
129                 .collect::<Vec<_>>();
130
131             let task = io::Task::AddRoot {
132                 root,
133                 path: path.clone(),
134                 root_filter,
135                 nested_roots,
136             };
137             res.worker.sender().send(task).unwrap();
138         }
139         let roots = res.roots.iter().map(|(id, _)| id).collect();
140         (res, roots)
141     }
142
143     pub fn root2path(&self, root: VfsRoot) -> PathBuf {
144         self.roots[root].root.clone()
145     }
146
147     pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
148         if let Some((_root, _path, Some(file))) = self.find_root(path) {
149             return Some(file);
150         }
151         None
152     }
153
154     pub fn file2path(&self, file: VfsFile) -> PathBuf {
155         let rel_path = &self.files[file].path;
156         let root_path = &self.roots[self.files[file].root].root;
157         rel_path.to_path(root_path)
158     }
159
160     pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
161         if let Some((_root, _path, Some(file))) = self.find_root(path) {
162             return Some(file);
163         }
164         None
165     }
166
167     pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
168         if let Some((root, rel_path, file)) = self.find_root(path) {
169             return if let Some(file) = file {
170                 Some(file)
171             } else {
172                 let text = fs::read_to_string(path).unwrap_or_default();
173                 let text = Arc::new(text);
174                 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
175                 let change = VfsChange::AddFile {
176                     file,
177                     text,
178                     root,
179                     path: rel_path,
180                 };
181                 self.pending_changes.push(change);
182                 Some(file)
183             };
184         }
185         None
186     }
187
188     pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
189         self.worker.receiver()
190     }
191
192     pub fn handle_task(&mut self, task: io::TaskResult) {
193         match task {
194             TaskResult::AddRoot(task) => {
195                 let mut files = Vec::new();
196                 // While we were scanning the root in the backgound, a file might have
197                 // been open in the editor, so we need to account for that.
198                 let exising = self.root2files[&task.root]
199                     .iter()
200                     .map(|&file| (self.files[file].path.clone(), file))
201                     .collect::<FxHashMap<_, _>>();
202                 for (path, text) in task.files {
203                     if let Some(&file) = exising.get(&path) {
204                         let text = Arc::clone(&self.files[file].text);
205                         files.push((file, path, text));
206                         continue;
207                     }
208                     let text = Arc::new(text);
209                     let file = self.add_file(task.root, path.clone(), Arc::clone(&text), false);
210                     files.push((file, path, text));
211                 }
212
213                 let change = VfsChange::AddRoot {
214                     root: task.root,
215                     files,
216                 };
217                 self.pending_changes.push(change);
218             }
219             TaskResult::HandleChange(change) => match &change {
220                 WatcherChange::Create(path) if path.is_dir() => {
221                     if let Some((root, _path, _file)) = self.find_root(&path) {
222                         let root_filter = self.roots[root].clone();
223                         self.worker
224                             .sender()
225                             .send(Task::Watch {
226                                 dir: path.to_path_buf(),
227                                 root_filter,
228                             })
229                             .unwrap()
230                     }
231                 }
232                 WatcherChange::Create(path)
233                 | WatcherChange::Remove(path)
234                 | WatcherChange::Write(path) => {
235                     if self.should_handle_change(&path) {
236                         self.worker.sender().send(Task::LoadChange(change)).unwrap()
237                     }
238                 }
239                 WatcherChange::Rescan => {
240                     // TODO we should reload all files
241                 }
242             },
243             TaskResult::LoadChange(change) => match change {
244                 WatcherChangeData::Create { path, text }
245                 | WatcherChangeData::Write { path, text } => {
246                     if let Some((root, path, file)) = self.find_root(&path) {
247                         if let Some(file) = file {
248                             self.do_change_file(file, text, false);
249                         } else {
250                             self.do_add_file(root, path, text, false);
251                         }
252                     }
253                 }
254                 WatcherChangeData::Remove { path } => {
255                     if let Some((root, path, file)) = self.find_root(&path) {
256                         if let Some(file) = file {
257                             self.do_remove_file(root, path, file, false);
258                         }
259                     }
260                 }
261             },
262             TaskResult::NoOp => {}
263         }
264     }
265
266     fn should_handle_change(&self, path: &Path) -> bool {
267         if let Some((_root, _rel_path, file)) = self.find_root(&path) {
268             if let Some(file) = file {
269                 if self.files[file].is_overlayed {
270                     // file is overlayed
271                     log::debug!("skipping overlayed \"{}\"", path.display());
272                     return false;
273                 }
274             }
275             true
276         } else {
277             // file doesn't belong to any root
278             false
279         }
280     }
281
282     fn do_add_file(
283         &mut self,
284         root: VfsRoot,
285         path: RelativePathBuf,
286         text: String,
287         is_overlay: bool,
288     ) -> Option<VfsFile> {
289         let text = Arc::new(text);
290         let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
291         self.pending_changes.push(VfsChange::AddFile {
292             file,
293             root,
294             path,
295             text,
296         });
297         Some(file)
298     }
299
300     fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
301         if !is_overlay && self.files[file].is_overlayed {
302             return;
303         }
304         let text = Arc::new(text);
305         self.change_file(file, text.clone(), is_overlay);
306         self.pending_changes
307             .push(VfsChange::ChangeFile { file, text });
308     }
309
310     fn do_remove_file(
311         &mut self,
312         root: VfsRoot,
313         path: RelativePathBuf,
314         file: VfsFile,
315         is_overlay: bool,
316     ) {
317         if !is_overlay && self.files[file].is_overlayed {
318             return;
319         }
320         self.remove_file(file);
321         self.pending_changes
322             .push(VfsChange::RemoveFile { root, path, file });
323     }
324
325     pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
326         if let Some((root, rel_path, file)) = self.find_root(path) {
327             if let Some(file) = file {
328                 self.do_change_file(file, text, true);
329                 Some(file)
330             } else {
331                 self.do_add_file(root, rel_path, text, true)
332             }
333         } else {
334             None
335         }
336     }
337
338     pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
339         if let Some((_root, _path, file)) = self.find_root(path) {
340             let file = file.expect("can't change a file which wasn't added");
341             self.do_change_file(file, new_text, true);
342         }
343     }
344
345     pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
346         if let Some((root, path, file)) = self.find_root(path) {
347             let file = file.expect("can't remove a file which wasn't added");
348             let full_path = path.to_path(&self.roots[root].root);
349             if let Ok(text) = fs::read_to_string(&full_path) {
350                 self.do_change_file(file, text, true);
351             } else {
352                 self.do_remove_file(root, path, file, true);
353             }
354             Some(file)
355         } else {
356             None
357         }
358     }
359
360     pub fn commit_changes(&mut self) -> Vec<VfsChange> {
361         mem::replace(&mut self.pending_changes, Vec::new())
362     }
363
364     /// Sutdown the VFS and terminate the background watching thread.
365     pub fn shutdown(self) -> thread::Result<()> {
366         self.worker.shutdown()
367     }
368
369     fn add_file(
370         &mut self,
371         root: VfsRoot,
372         path: RelativePathBuf,
373         text: Arc<String>,
374         is_overlayed: bool,
375     ) -> VfsFile {
376         let data = VfsFileData {
377             root,
378             path,
379             text,
380             is_overlayed,
381         };
382         let file = self.files.alloc(data);
383         self.root2files.get_mut(&root).unwrap().insert(file);
384         file
385     }
386
387     fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
388         let mut file_data = &mut self.files[file];
389         file_data.text = new_text;
390         file_data.is_overlayed = is_overlayed;
391     }
392
393     fn remove_file(&mut self, file: VfsFile) {
394         //FIXME: use arena with removal
395         self.files[file].text = Default::default();
396         self.files[file].path = Default::default();
397         let root = self.files[file].root;
398         let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
399         assert!(removed);
400     }
401
402     fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
403         let (root, path) = self
404             .roots
405             .iter()
406             .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?;
407         let file = self.root2files[&root]
408             .iter()
409             .map(|&it| it)
410             .find(|&file| self.files[file].path == path);
411         Some((root, path, file))
412     }
413 }
414
415 #[derive(Debug, Clone)]
416 pub enum VfsChange {
417     AddRoot {
418         root: VfsRoot,
419         files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
420     },
421     AddFile {
422         root: VfsRoot,
423         file: VfsFile,
424         path: RelativePathBuf,
425         text: Arc<String>,
426     },
427     RemoveFile {
428         root: VfsRoot,
429         file: VfsFile,
430         path: RelativePathBuf,
431     },
432     ChangeFile {
433         file: VfsFile,
434         text: Arc<String>,
435     },
436 }