]> git.lizzy.rs Git - rust.git/blob - crates/ra_vfs/src/lib.rs
Merge #358
[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 //! whihc are watched for changes. Typically, there will be a root for each
15 //! Cargo package.
16 mod arena;
17 mod io;
18
19 use std::{
20     fmt,
21     mem,
22     thread,
23     cmp::Reverse,
24     path::{Path, PathBuf},
25     ffi::OsStr,
26     sync::Arc,
27     fs,
28 };
29
30 use rustc_hash::{FxHashMap, FxHashSet};
31 use relative_path::RelativePathBuf;
32 use crossbeam_channel::Receiver;
33 use walkdir::DirEntry;
34 use thread_worker::WorkerHandle;
35
36 use crate::{
37     arena::{ArenaId, Arena},
38 };
39
40 pub use crate::io::TaskResult as VfsTask;
41
42 /// `RootFilter` is a predicate that checks if a file can belong to a root. If
43 /// several filters match a file (nested dirs), the most nested one wins.
44 struct RootFilter {
45     root: PathBuf,
46     file_filter: fn(&Path) -> bool,
47 }
48
49 impl RootFilter {
50     fn new(root: PathBuf) -> RootFilter {
51         RootFilter {
52             root,
53             file_filter: has_rs_extension,
54         }
55     }
56     /// Check if this root can contain `path`. NB: even if this returns
57     /// true, the `path` might actually be conained in some nested root.
58     fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
59         if !(self.file_filter)(path) {
60             return None;
61         }
62         let path = path.strip_prefix(&self.root).ok()?;
63         RelativePathBuf::from_path(path).ok()
64     }
65 }
66
67 fn has_rs_extension(p: &Path) -> bool {
68     p.extension() == Some(OsStr::new("rs"))
69 }
70
71 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
72 pub struct VfsRoot(pub u32);
73
74 impl ArenaId for VfsRoot {
75     fn from_u32(idx: u32) -> VfsRoot {
76         VfsRoot(idx)
77     }
78     fn to_u32(self) -> u32 {
79         self.0
80     }
81 }
82
83 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
84 pub struct VfsFile(pub u32);
85
86 impl ArenaId for VfsFile {
87     fn from_u32(idx: u32) -> VfsFile {
88         VfsFile(idx)
89     }
90     fn to_u32(self) -> u32 {
91         self.0
92     }
93 }
94
95 struct VfsFileData {
96     root: VfsRoot,
97     path: RelativePathBuf,
98     text: Arc<String>,
99 }
100
101 pub struct Vfs {
102     roots: Arena<VfsRoot, RootFilter>,
103     files: Arena<VfsFile, VfsFileData>,
104     root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
105     pending_changes: Vec<VfsChange>,
106     worker: io::Worker,
107     worker_handle: WorkerHandle,
108 }
109
110 impl fmt::Debug for Vfs {
111     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112         f.write_str("Vfs { ... }")
113     }
114 }
115
116 impl Vfs {
117     pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
118         let (worker, worker_handle) = io::start();
119
120         let mut res = Vfs {
121             roots: Arena::default(),
122             files: Arena::default(),
123             root2files: FxHashMap::default(),
124             worker,
125             worker_handle,
126             pending_changes: Vec::new(),
127         };
128
129         // A hack to make nesting work.
130         roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
131         for (i, path) in roots.iter().enumerate() {
132             let root = res.roots.alloc(RootFilter::new(path.clone()));
133             res.root2files.insert(root, Default::default());
134             let nested = roots[..i]
135                 .iter()
136                 .filter(|it| it.starts_with(path))
137                 .map(|it| it.clone())
138                 .collect::<Vec<_>>();
139             let filter = move |entry: &DirEntry| {
140                 if entry.file_type().is_file() {
141                     has_rs_extension(entry.path())
142                 } else {
143                     nested.iter().all(|it| it != entry.path())
144                 }
145             };
146             let task = io::Task {
147                 root,
148                 path: path.clone(),
149                 filter: Box::new(filter),
150             };
151             res.worker.inp.send(task);
152         }
153         let roots = res.roots.iter().map(|(id, _)| id).collect();
154         (res, roots)
155     }
156
157     pub fn root2path(&self, root: VfsRoot) -> PathBuf {
158         self.roots[root].root.clone()
159     }
160
161     pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
162         if let Some((_root, _path, Some(file))) = self.find_root(path) {
163             return Some(file);
164         }
165         None
166     }
167
168     pub fn file2path(&self, file: VfsFile) -> PathBuf {
169         let rel_path = &self.files[file].path;
170         let root_path = &self.roots[self.files[file].root].root;
171         rel_path.to_path(root_path)
172     }
173
174     pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
175         if let Some((_root, _path, Some(file))) = self.find_root(path) {
176             return Some(file);
177         }
178         None
179     }
180
181     pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
182         if let Some((root, rel_path, file)) = self.find_root(path) {
183             return if let Some(file) = file {
184                 Some(file)
185             } else {
186                 let text = fs::read_to_string(path).unwrap_or_default();
187                 let text = Arc::new(text);
188                 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text));
189                 let change = VfsChange::AddFile {
190                     file,
191                     text,
192                     root,
193                     path: rel_path,
194                 };
195                 self.pending_changes.push(change);
196                 Some(file)
197             };
198         }
199         None
200     }
201
202     pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
203         &self.worker.out
204     }
205
206     pub fn handle_task(&mut self, task: io::TaskResult) {
207         let mut files = Vec::new();
208         // While we were scanning the root in the backgound, a file might have
209         // been open in the editor, so we need to account for that.
210         let exising = self.root2files[&task.root]
211             .iter()
212             .map(|&file| (self.files[file].path.clone(), file))
213             .collect::<FxHashMap<_, _>>();
214         for (path, text) in task.files {
215             if let Some(&file) = exising.get(&path) {
216                 let text = Arc::clone(&self.files[file].text);
217                 files.push((file, path, text));
218                 continue;
219             }
220             let text = Arc::new(text);
221             let file = self.add_file(task.root, path.clone(), Arc::clone(&text));
222             files.push((file, path, text));
223         }
224
225         let change = VfsChange::AddRoot {
226             root: task.root,
227             files,
228         };
229         self.pending_changes.push(change);
230     }
231
232     pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
233         let mut res = None;
234         if let Some((root, path, file)) = self.find_root(path) {
235             let text = Arc::new(text);
236             let change = if let Some(file) = file {
237                 res = Some(file);
238                 self.change_file(file, Arc::clone(&text));
239                 VfsChange::ChangeFile { file, text }
240             } else {
241                 let file = self.add_file(root, path.clone(), Arc::clone(&text));
242                 res = Some(file);
243                 VfsChange::AddFile {
244                     file,
245                     text,
246                     root,
247                     path,
248                 }
249             };
250             self.pending_changes.push(change);
251         }
252         res
253     }
254
255     pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
256         if let Some((_root, _path, file)) = self.find_root(path) {
257             let file = file.expect("can't change a file which wasn't added");
258             let text = Arc::new(new_text);
259             self.change_file(file, Arc::clone(&text));
260             let change = VfsChange::ChangeFile { file, text };
261             self.pending_changes.push(change);
262         }
263     }
264
265     pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
266         let mut res = None;
267         if let Some((root, path, file)) = self.find_root(path) {
268             let file = file.expect("can't remove a file which wasn't added");
269             res = Some(file);
270             let full_path = path.to_path(&self.roots[root].root);
271             let change = if let Ok(text) = fs::read_to_string(&full_path) {
272                 let text = Arc::new(text);
273                 self.change_file(file, Arc::clone(&text));
274                 VfsChange::ChangeFile { file, text }
275             } else {
276                 self.remove_file(file);
277                 VfsChange::RemoveFile { root, file, path }
278             };
279             self.pending_changes.push(change);
280         }
281         res
282     }
283
284     pub fn commit_changes(&mut self) -> Vec<VfsChange> {
285         mem::replace(&mut self.pending_changes, Vec::new())
286     }
287
288     /// Sutdown the VFS and terminate the background watching thread.
289     pub fn shutdown(self) -> thread::Result<()> {
290         let _ = self.worker.shutdown();
291         self.worker_handle.shutdown()
292     }
293
294     fn add_file(&mut self, root: VfsRoot, path: RelativePathBuf, text: Arc<String>) -> VfsFile {
295         let data = VfsFileData { root, path, text };
296         let file = self.files.alloc(data);
297         self.root2files.get_mut(&root).unwrap().insert(file);
298         file
299     }
300
301     fn change_file(&mut self, file: VfsFile, new_text: Arc<String>) {
302         self.files[file].text = new_text;
303     }
304
305     fn remove_file(&mut self, file: VfsFile) {
306         //FIXME: use arena with removal
307         self.files[file].text = Default::default();
308         self.files[file].path = Default::default();
309         let root = self.files[file].root;
310         let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
311         assert!(removed);
312     }
313
314     fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
315         let (root, path) = self
316             .roots
317             .iter()
318             .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?;
319         let file = self.root2files[&root]
320             .iter()
321             .map(|&it| it)
322             .find(|&file| self.files[file].path == path);
323         Some((root, path, file))
324     }
325 }
326
327 #[derive(Debug, Clone)]
328 pub enum VfsChange {
329     AddRoot {
330         root: VfsRoot,
331         files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
332     },
333     AddFile {
334         root: VfsRoot,
335         file: VfsFile,
336         path: RelativePathBuf,
337         text: Arc<String>,
338     },
339     RemoveFile {
340         root: VfsRoot,
341         file: VfsFile,
342         path: RelativePathBuf,
343     },
344     ChangeFile {
345         file: VfsFile,
346         text: Arc<String>,
347     },
348 }