]> git.lizzy.rs Git - rust.git/blob - crates/ra_vfs/src/lib.rs
Merge #662
[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, map::ArenaMap};
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::{TaskResult, Worker};
33
34 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35 pub struct VfsRoot(pub RawId);
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 {
44     root: PathBuf,
45     excluded_dirs: Vec<PathBuf>,
46 }
47
48 pub(crate) struct Roots {
49     roots: Arena<VfsRoot, Arc<RootConfig>>,
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 {
62             root,
63             excluded_dirs,
64         }
65     }
66     /// Cheks if root contains a path and returns a root-relative path.
67     pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> {
68         // First, check excluded dirs
69         if self.excluded_dirs.iter().any(|it| path.starts_with(it)) {
70             return None;
71         }
72         let rel_path = path.strip_prefix(&self.root).ok()?;
73         let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
74
75         // Ignore some common directories.
76         //
77         // FIXME: don't hard-code, specify at source-root creation time using
78         // gitignore
79         for (i, c) in rel_path.components().enumerate() {
80             if let Component::Normal(c) = c {
81                 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
82                     return None;
83                 }
84             }
85         }
86
87         if path.is_file() && rel_path.extension() != Some("rs") {
88             return None;
89         }
90
91         Some(rel_path)
92     }
93 }
94
95 impl Roots {
96     pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
97         let mut roots = Arena::default();
98         // A hack to make nesting work.
99         paths.sort_by_key(|it| Reverse(it.as_os_str().len()));
100         for (i, path) in paths.iter().enumerate() {
101             let nested_roots = paths[..i]
102                 .iter()
103                 .filter(|it| it.starts_with(path))
104                 .map(|it| it.clone())
105                 .collect::<Vec<_>>();
106
107             let config = Arc::new(RootConfig::new(path.clone(), nested_roots));
108
109             roots.alloc(config.clone());
110         }
111         Roots { roots }
112     }
113     pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
114         self.roots
115             .iter()
116             .find_map(|(root, data)| data.contains(path).map(|it| (root, it)))
117     }
118 }
119
120 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
121 pub struct VfsFile(pub RawId);
122 impl_arena_id!(VfsFile);
123
124 struct VfsFileData {
125     root: VfsRoot,
126     path: RelativePathBuf,
127     is_overlayed: bool,
128     text: Arc<String>,
129 }
130
131 pub struct Vfs {
132     roots: Arc<Roots>,
133     files: Arena<VfsFile, VfsFileData>,
134     root2files: ArenaMap<VfsRoot, FxHashSet<VfsFile>>,
135     pending_changes: Vec<VfsChange>,
136     worker: Worker,
137 }
138
139 impl fmt::Debug for Vfs {
140     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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()
146     }
147 }
148
149 impl Vfs {
150     pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
151         let roots = Arc::new(Roots::new(roots));
152         let worker = io::Worker::start(Arc::clone(&roots));
153         let mut root2files = ArenaMap::default();
154
155         for (root, config) in roots.iter() {
156             root2files.insert(root, Default::default());
157             worker
158                 .sender()
159                 .send(io::Task::AddRoot {
160                     root,
161                     config: Arc::clone(config),
162                 })
163                 .unwrap();
164         }
165         let res = Vfs {
166             roots,
167             files: Arena::default(),
168             root2files,
169             worker,
170             pending_changes: Vec::new(),
171         };
172         let vfs_roots = res.roots.iter().map(|(id, _)| id).collect();
173         (res, vfs_roots)
174     }
175
176     pub fn root2path(&self, root: VfsRoot) -> PathBuf {
177         self.roots[root].root.clone()
178     }
179
180     pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
181         if let Some((_root, _path, Some(file))) = self.find_root(path) {
182             return Some(file);
183         }
184         None
185     }
186
187     pub fn file2path(&self, file: VfsFile) -> PathBuf {
188         let rel_path = &self.files[file].path;
189         let root_path = &self.roots[self.files[file].root].root;
190         rel_path.to_path(root_path)
191     }
192
193     pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
194         if let Some((_root, _path, Some(file))) = self.find_root(path) {
195             return Some(file);
196         }
197         None
198     }
199
200     pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
201         if let Some((root, rel_path, file)) = self.find_root(path) {
202             return if let Some(file) = file {
203                 Some(file)
204             } else {
205                 let text = fs::read_to_string(path).unwrap_or_default();
206                 let text = Arc::new(text);
207                 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
208                 let change = VfsChange::AddFile {
209                     file,
210                     text,
211                     root,
212                     path: rel_path,
213                 };
214                 self.pending_changes.push(change);
215                 Some(file)
216             };
217         }
218         None
219     }
220
221     pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
222         self.worker.receiver()
223     }
224
225     pub fn handle_task(&mut self, task: io::TaskResult) {
226         match task {
227             TaskResult::BulkLoadRoot { root, files } => {
228                 let mut cur_files = Vec::new();
229                 // While we were scanning the root in the backgound, a file might have
230                 // been open in the editor, so we need to account for that.
231                 let exising = self.root2files[root]
232                     .iter()
233                     .map(|&file| (self.files[file].path.clone(), file))
234                     .collect::<FxHashMap<_, _>>();
235                 for (path, text) in files {
236                     if let Some(&file) = exising.get(&path) {
237                         let text = Arc::clone(&self.files[file].text);
238                         cur_files.push((file, path, text));
239                         continue;
240                     }
241                     let text = Arc::new(text);
242                     let file = self.add_file(root, path.clone(), Arc::clone(&text), false);
243                     cur_files.push((file, path, text));
244                 }
245
246                 let change = VfsChange::AddRoot {
247                     root,
248                     files: cur_files,
249                 };
250                 self.pending_changes.push(change);
251             }
252             TaskResult::AddSingleFile { root, path, text } => {
253                 if self.find_file(root, &path).is_none() {
254                     self.do_add_file(root, path, text, false);
255                 }
256             }
257             TaskResult::ChangeSingleFile { root, path, text } => {
258                 if let Some(file) = self.find_file(root, &path) {
259                     self.do_change_file(file, text, false);
260                 } else {
261                     self.do_add_file(root, path, text, false);
262                 }
263             }
264             TaskResult::RemoveSingleFile { root, path } => {
265                 if let Some(file) = self.find_file(root, &path) {
266                     self.do_remove_file(root, path, file, false);
267                 }
268             }
269         }
270     }
271
272     fn do_add_file(
273         &mut self,
274         root: VfsRoot,
275         path: RelativePathBuf,
276         text: String,
277         is_overlay: bool,
278     ) -> Option<VfsFile> {
279         let text = Arc::new(text);
280         let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
281         self.pending_changes.push(VfsChange::AddFile {
282             file,
283             root,
284             path,
285             text,
286         });
287         Some(file)
288     }
289
290     fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
291         if !is_overlay && self.files[file].is_overlayed {
292             return;
293         }
294         let text = Arc::new(text);
295         self.change_file(file, text.clone(), is_overlay);
296         self.pending_changes
297             .push(VfsChange::ChangeFile { file, text });
298     }
299
300     fn do_remove_file(
301         &mut self,
302         root: VfsRoot,
303         path: RelativePathBuf,
304         file: VfsFile,
305         is_overlay: bool,
306     ) {
307         if !is_overlay && self.files[file].is_overlayed {
308             return;
309         }
310         self.remove_file(file);
311         self.pending_changes
312             .push(VfsChange::RemoveFile { root, path, file });
313     }
314
315     pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
316         if let Some((root, rel_path, file)) = self.find_root(path) {
317             if let Some(file) = file {
318                 self.do_change_file(file, text, true);
319                 Some(file)
320             } else {
321                 self.do_add_file(root, rel_path, text, true)
322             }
323         } else {
324             None
325         }
326     }
327
328     pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
329         if let Some((_root, _path, file)) = self.find_root(path) {
330             let file = file.expect("can't change a file which wasn't added");
331             self.do_change_file(file, new_text, true);
332         }
333     }
334
335     pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
336         if let Some((root, path, file)) = self.find_root(path) {
337             let file = file.expect("can't remove a file which wasn't added");
338             let full_path = path.to_path(&self.roots[root].root);
339             if let Ok(text) = fs::read_to_string(&full_path) {
340                 self.do_change_file(file, text, true);
341             } else {
342                 self.do_remove_file(root, path, file, true);
343             }
344             Some(file)
345         } else {
346             None
347         }
348     }
349
350     pub fn commit_changes(&mut self) -> Vec<VfsChange> {
351         mem::replace(&mut self.pending_changes, Vec::new())
352     }
353
354     /// Sutdown the VFS and terminate the background watching thread.
355     pub fn shutdown(self) -> thread::Result<()> {
356         self.worker.shutdown()
357     }
358
359     fn add_file(
360         &mut self,
361         root: VfsRoot,
362         path: RelativePathBuf,
363         text: Arc<String>,
364         is_overlayed: bool,
365     ) -> VfsFile {
366         let data = VfsFileData {
367             root,
368             path,
369             text,
370             is_overlayed,
371         };
372         let file = self.files.alloc(data);
373         self.root2files.get_mut(root).unwrap().insert(file);
374         file
375     }
376
377     fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
378         let mut file_data = &mut self.files[file];
379         file_data.text = new_text;
380         file_data.is_overlayed = is_overlayed;
381     }
382
383     fn remove_file(&mut self, file: VfsFile) {
384         //FIXME: use arena with removal
385         self.files[file].text = Default::default();
386         self.files[file].path = Default::default();
387         let root = self.files[file].root;
388         let removed = self.root2files.get_mut(root).unwrap().remove(&file);
389         assert!(removed);
390     }
391
392     fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
393         let (root, path) = self.roots.find(&path)?;
394         let file = self.find_file(root, &path);
395         Some((root, path, file))
396     }
397
398     fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
399         self.root2files[root]
400             .iter()
401             .map(|&it| it)
402             .find(|&file| self.files[file].path == path)
403     }
404 }
405
406 #[derive(Debug, Clone)]
407 pub enum VfsChange {
408     AddRoot {
409         root: VfsRoot,
410         files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
411     },
412     AddFile {
413         root: VfsRoot,
414         file: VfsFile,
415         path: RelativePathBuf,
416         text: Arc<String>,
417     },
418     RemoveFile {
419         root: VfsRoot,
420         file: VfsFile,
421         path: RelativePathBuf,
422     },
423     ChangeFile {
424         file: VfsFile,
425         text: Arc<String>,
426     },
427 }