]> git.lizzy.rs Git - rust.git/blob - crates/ra_vfs/src/lib.rs
Merge #834
[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 //!
11 //! TODO: Some LSP clients support watching the disk, so this crate should to
12 //! support custom watcher events (related to
13 //! <https://github.com/rust-analyzer/rust-analyzer/issues/131>)
14 //!
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
17 //! Cargo package.
18 mod io;
19
20 use std::{
21     cmp::Reverse,
22     fmt, fs, mem,
23     path::{Path, PathBuf},
24     sync::Arc,
25 };
26
27 use crossbeam_channel::Receiver;
28 use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap};
29 use relative_path::{Component, RelativePath, RelativePathBuf};
30 use rustc_hash::{FxHashMap, FxHashSet};
31
32 pub use crate::io::TaskResult as VfsTask;
33 use io::{TaskResult, Worker};
34
35 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
36 pub struct VfsRoot(pub RawId);
37 impl_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
44 pub(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
51 pub(crate) struct Roots {
52     roots: Arena<VfsRoot, Arc<RootConfig>>,
53 }
54
55 impl std::ops::Deref for Roots {
56     type Target = Arena<VfsRoot, Arc<RootConfig>>;
57     fn deref(&self) -> &Self::Target {
58         &self.roots
59     }
60 }
61
62 impl 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
106 impl Roots {
107     pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
108         let mut roots = Arena::default();
109         // A hack to make nesting work.
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
130 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
131 pub struct VfsFile(pub RawId);
132 impl_arena_id!(VfsFile);
133
134 struct VfsFileData {
135     root: VfsRoot,
136     path: RelativePathBuf,
137     is_overlayed: bool,
138     text: Arc<String>,
139 }
140
141 pub struct Vfs {
142     roots: Arc<Roots>,
143     files: Arena<VfsFile, VfsFileData>,
144     root2files: ArenaMap<VfsRoot, FxHashSet<VfsFile>>,
145     pending_changes: Vec<VfsChange>,
146     worker: Worker,
147 }
148
149 impl fmt::Debug for Vfs {
150     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
151         f.debug_struct("Vfs")
152             .field("n_roots", &self.roots.len())
153             .field("n_files", &self.files.len())
154             .field("n_pending_changes", &self.pending_changes.len())
155             .finish()
156     }
157 }
158
159 impl Vfs {
160     pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
161         let roots = Arc::new(Roots::new(roots));
162         let worker = io::start(Arc::clone(&roots));
163         let mut root2files = ArenaMap::default();
164
165         for (root, config) in roots.iter() {
166             root2files.insert(root, Default::default());
167             worker.sender().send(io::Task::AddRoot { root, config: Arc::clone(config) }).unwrap();
168         }
169         let res =
170             Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() };
171         let vfs_roots = res.roots.iter().map(|(id, _)| id).collect();
172         (res, vfs_roots)
173     }
174
175     pub fn root2path(&self, root: VfsRoot) -> PathBuf {
176         self.roots[root].root.clone()
177     }
178
179     pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
180         if let Some((_root, _path, Some(file))) = self.find_root(path) {
181             return Some(file);
182         }
183         None
184     }
185
186     pub fn file2path(&self, file: VfsFile) -> PathBuf {
187         let rel_path = &self.files[file].path;
188         let root_path = &self.roots[self.files[file].root].root;
189         rel_path.to_path(root_path)
190     }
191
192     pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
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()
201     }
202
203     pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
204         if let Some((root, rel_path, file)) = self.find_root(path) {
205             return if let Some(file) = file {
206                 Some(file)
207             } else {
208                 let text = fs::read_to_string(path).unwrap_or_default();
209                 let text = Arc::new(text);
210                 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
211                 let change = VfsChange::AddFile { file, text, root, path: rel_path };
212                 self.pending_changes.push(change);
213                 Some(file)
214             };
215         }
216         None
217     }
218
219     pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
220         self.worker.receiver()
221     }
222
223     pub fn handle_task(&mut self, task: io::TaskResult) {
224         match task {
225             TaskResult::BulkLoadRoot { root, files } => {
226                 let mut cur_files = Vec::new();
227                 // While we were scanning the root in the background, a file might have
228                 // been open in the editor, so we need to account for that.
229                 let existing = self.root2files[root]
230                     .iter()
231                     .map(|&file| (self.files[file].path.clone(), file))
232                     .collect::<FxHashMap<_, _>>();
233                 for (path, text) in files {
234                     if let Some(&file) = existing.get(&path) {
235                         let text = Arc::clone(&self.files[file].text);
236                         cur_files.push((file, path, text));
237                         continue;
238                     }
239                     let text = Arc::new(text);
240                     let file = self.add_file(root, path.clone(), Arc::clone(&text), false);
241                     cur_files.push((file, path, text));
242                 }
243
244                 let change = VfsChange::AddRoot { root, files: cur_files };
245                 self.pending_changes.push(change);
246             }
247             TaskResult::SingleFile { root, path, text } => {
248                 match (self.find_file(root, &path), text) {
249                     (Some(file), None) => {
250                         self.do_remove_file(root, path, file, false);
251                     }
252                     (None, Some(text)) => {
253                         self.do_add_file(root, path, text, false);
254                     }
255                     (Some(file), Some(text)) => {
256                         self.do_change_file(file, text, false);
257                     }
258                     (None, None) => (),
259                 }
260             }
261         }
262     }
263
264     fn do_add_file(
265         &mut self,
266         root: VfsRoot,
267         path: RelativePathBuf,
268         text: String,
269         is_overlay: bool,
270     ) -> Option<VfsFile> {
271         let text = Arc::new(text);
272         let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
273         self.pending_changes.push(VfsChange::AddFile { file, root, path, text });
274         Some(file)
275     }
276
277     fn do_change_file(&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);
282         self.change_file(file, text.clone(), is_overlay);
283         self.pending_changes.push(VfsChange::ChangeFile { file, text });
284     }
285
286     fn do_remove_file(
287         &mut self,
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 });
298     }
299
300     pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
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
339     fn add_file(
340         &mut self,
341         root: VfsRoot,
342         path: RelativePathBuf,
343         text: Arc<String>,
344         is_overlayed: bool,
345     ) -> VfsFile {
346         let data = VfsFileData { root, path, text, is_overlayed };
347         let file = self.files.alloc(data);
348         self.root2files.get_mut(root).unwrap().insert(file);
349         file
350     }
351
352     fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
353         let mut file_data = &mut self.files[file];
354         file_data.text = new_text;
355         file_data.is_overlayed = is_overlayed;
356     }
357
358     fn remove_file(&mut self, file: VfsFile) {
359         // FIXME: use arena with removal
360         self.files[file].text = Default::default();
361         self.files[file].path = Default::default();
362         let root = self.files[file].root;
363         let removed = self.root2files.get_mut(root).unwrap().remove(&file);
364         assert!(removed);
365     }
366
367     fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
368         let (root, path) = self.roots.find(&path)?;
369         let file = self.find_file(root, &path);
370         Some((root, path, file))
371     }
372
373     fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
374         self.root2files[root].iter().map(|&it| it).find(|&file| self.files[file].path == path)
375     }
376 }
377
378 #[derive(Debug, Clone)]
379 pub enum VfsChange {
380     AddRoot { root: VfsRoot, files: Vec<(VfsFile, RelativePathBuf, Arc<String>)> },
381     AddFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf, text: Arc<String> },
382     RemoveFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf },
383     ChangeFile { file: VfsFile, text: Arc<String> },
384 }