]> git.lizzy.rs Git - rust.git/blob - crates/ra_vfs/src/lib.rs
Merge #818
[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     thread,
26 };
27
28 use crossbeam_channel::Receiver;
29 use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap};
30 use relative_path::{Component, RelativePath, RelativePathBuf};
31 use rustc_hash::{FxHashMap, FxHashSet};
32
33 pub use crate::io::TaskResult as VfsTask;
34 use io::{TaskResult, Worker};
35
36 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
37 pub struct VfsRoot(pub RawId);
38 impl_arena_id!(VfsRoot);
39
40 /// Describes the contents of a single source root.
41 ///
42 /// `RootConfig` can be thought of as a glob pattern like `src/**.rs` which
43 /// specifies the source root or as a function which takes a `PathBuf` and
44 /// returns `true` iff path belongs to the source root
45 pub(crate) struct RootConfig {
46     root: PathBuf,
47     // result of `root.canonicalize()` if that differs from `root`; `None` otherwise.
48     canonical_root: Option<PathBuf>,
49     excluded_dirs: Vec<PathBuf>,
50 }
51
52 pub(crate) struct Roots {
53     roots: Arena<VfsRoot, Arc<RootConfig>>,
54 }
55
56 impl std::ops::Deref for Roots {
57     type Target = Arena<VfsRoot, Arc<RootConfig>>;
58     fn deref(&self) -> &Self::Target {
59         &self.roots
60     }
61 }
62
63 impl RootConfig {
64     fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootConfig {
65         let mut canonical_root = root.canonicalize().ok();
66         if Some(&root) == canonical_root.as_ref() {
67             canonical_root = None;
68         }
69         RootConfig { root, canonical_root, excluded_dirs }
70     }
71     /// Checks if root contains a path and returns a root-relative path.
72     pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> {
73         // First, check excluded dirs
74         if self.excluded_dirs.iter().any(|it| path.starts_with(it)) {
75             return None;
76         }
77         let rel_path = path
78             .strip_prefix(&self.root)
79             .or_else(|err_payload| {
80                 self.canonical_root
81                     .as_ref()
82                     .map_or(Err(err_payload), |canonical_root| path.strip_prefix(canonical_root))
83             })
84             .ok()?;
85         let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
86
87         // Ignore some common directories.
88         //
89         // FIXME: don't hard-code, specify at source-root creation time using
90         // gitignore
91         for (i, c) in rel_path.components().enumerate() {
92             if let Component::Normal(c) = c {
93                 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
94                     return None;
95                 }
96             }
97         }
98
99         if path.is_file() && rel_path.extension() != Some("rs") {
100             return None;
101         }
102
103         Some(rel_path)
104     }
105 }
106
107 impl Roots {
108     pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
109         let mut roots = Arena::default();
110         // A hack to make nesting work.
111         paths.sort_by_key(|it| Reverse(it.as_os_str().len()));
112         paths.dedup();
113         for (i, path) in paths.iter().enumerate() {
114             let nested_roots = paths[..i]
115                 .iter()
116                 .filter(|it| it.starts_with(path))
117                 .map(|it| it.clone())
118                 .collect::<Vec<_>>();
119
120             let config = Arc::new(RootConfig::new(path.clone(), nested_roots));
121
122             roots.alloc(config.clone());
123         }
124         Roots { roots }
125     }
126     pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
127         self.roots.iter().find_map(|(root, data)| data.contains(path).map(|it| (root, it)))
128     }
129 }
130
131 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
132 pub struct VfsFile(pub RawId);
133 impl_arena_id!(VfsFile);
134
135 struct VfsFileData {
136     root: VfsRoot,
137     path: RelativePathBuf,
138     is_overlayed: bool,
139     text: Arc<String>,
140 }
141
142 pub struct Vfs {
143     roots: Arc<Roots>,
144     files: Arena<VfsFile, VfsFileData>,
145     root2files: ArenaMap<VfsRoot, FxHashSet<VfsFile>>,
146     pending_changes: Vec<VfsChange>,
147     worker: Worker,
148 }
149
150 impl fmt::Debug for Vfs {
151     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152         f.debug_struct("Vfs")
153             .field("n_roots", &self.roots.len())
154             .field("n_files", &self.files.len())
155             .field("n_pending_changes", &self.pending_changes.len())
156             .finish()
157     }
158 }
159
160 impl Vfs {
161     pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
162         let roots = Arc::new(Roots::new(roots));
163         let worker = io::Worker::start(Arc::clone(&roots));
164         let mut root2files = ArenaMap::default();
165
166         for (root, config) in roots.iter() {
167             root2files.insert(root, Default::default());
168             worker.sender().send(io::Task::AddRoot { root, config: Arc::clone(config) }).unwrap();
169         }
170         let res =
171             Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() };
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 num_roots(&self) -> usize {
201         self.roots.len()
202     }
203
204     pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
205         if let Some((root, rel_path, file)) = self.find_root(path) {
206             return if let Some(file) = file {
207                 Some(file)
208             } else {
209                 let text = fs::read_to_string(path).unwrap_or_default();
210                 let text = Arc::new(text);
211                 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
212                 let change = VfsChange::AddFile { file, text, root, path: rel_path };
213                 self.pending_changes.push(change);
214                 Some(file)
215             };
216         }
217         None
218     }
219
220     pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
221         self.worker.receiver()
222     }
223
224     pub fn handle_task(&mut self, task: io::TaskResult) {
225         match task {
226             TaskResult::BulkLoadRoot { root, files } => {
227                 let mut cur_files = Vec::new();
228                 // While we were scanning the root in the background, a file might have
229                 // been open in the editor, so we need to account for that.
230                 let existing = self.root2files[root]
231                     .iter()
232                     .map(|&file| (self.files[file].path.clone(), file))
233                     .collect::<FxHashMap<_, _>>();
234                 for (path, text) in files {
235                     if let Some(&file) = existing.get(&path) {
236                         let text = Arc::clone(&self.files[file].text);
237                         cur_files.push((file, path, text));
238                         continue;
239                     }
240                     let text = Arc::new(text);
241                     let file = self.add_file(root, path.clone(), Arc::clone(&text), false);
242                     cur_files.push((file, path, text));
243                 }
244
245                 let change = VfsChange::AddRoot { root, files: cur_files };
246                 self.pending_changes.push(change);
247             }
248             TaskResult::SingleFile { root, path, text } => {
249                 match (self.find_file(root, &path), text) {
250                     (Some(file), None) => {
251                         self.do_remove_file(root, path, file, false);
252                     }
253                     (None, Some(text)) => {
254                         self.do_add_file(root, path, text, false);
255                     }
256                     (Some(file), Some(text)) => {
257                         self.do_change_file(file, text, false);
258                     }
259                     (None, None) => (),
260                 }
261             }
262         }
263     }
264
265     fn do_add_file(
266         &mut self,
267         root: VfsRoot,
268         path: RelativePathBuf,
269         text: String,
270         is_overlay: bool,
271     ) -> Option<VfsFile> {
272         let text = Arc::new(text);
273         let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
274         self.pending_changes.push(VfsChange::AddFile { file, root, path, text });
275         Some(file)
276     }
277
278     fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
279         if !is_overlay && self.files[file].is_overlayed {
280             return;
281         }
282         let text = Arc::new(text);
283         self.change_file(file, text.clone(), is_overlay);
284         self.pending_changes.push(VfsChange::ChangeFile { file, text });
285     }
286
287     fn do_remove_file(
288         &mut self,
289         root: VfsRoot,
290         path: RelativePathBuf,
291         file: VfsFile,
292         is_overlay: bool,
293     ) {
294         if !is_overlay && self.files[file].is_overlayed {
295             return;
296         }
297         self.remove_file(file);
298         self.pending_changes.push(VfsChange::RemoveFile { root, path, file });
299     }
300
301     pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
302         if let Some((root, rel_path, file)) = self.find_root(path) {
303             if let Some(file) = file {
304                 self.do_change_file(file, text, true);
305                 Some(file)
306             } else {
307                 self.do_add_file(root, rel_path, text, true)
308             }
309         } else {
310             None
311         }
312     }
313
314     pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
315         if let Some((_root, _path, file)) = self.find_root(path) {
316             let file = file.expect("can't change a file which wasn't added");
317             self.do_change_file(file, new_text, true);
318         }
319     }
320
321     pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
322         if let Some((root, path, file)) = self.find_root(path) {
323             let file = file.expect("can't remove a file which wasn't added");
324             let full_path = path.to_path(&self.roots[root].root);
325             if let Ok(text) = fs::read_to_string(&full_path) {
326                 self.do_change_file(file, text, true);
327             } else {
328                 self.do_remove_file(root, path, file, true);
329             }
330             Some(file)
331         } else {
332             None
333         }
334     }
335
336     pub fn commit_changes(&mut self) -> Vec<VfsChange> {
337         mem::replace(&mut self.pending_changes, Vec::new())
338     }
339
340     /// Shutdown the VFS and terminate the background watching thread.
341     pub fn shutdown(self) -> thread::Result<()> {
342         self.worker.shutdown()
343     }
344
345     fn add_file(
346         &mut self,
347         root: VfsRoot,
348         path: RelativePathBuf,
349         text: Arc<String>,
350         is_overlayed: bool,
351     ) -> VfsFile {
352         let data = VfsFileData { root, path, text, is_overlayed };
353         let file = self.files.alloc(data);
354         self.root2files.get_mut(root).unwrap().insert(file);
355         file
356     }
357
358     fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
359         let mut file_data = &mut self.files[file];
360         file_data.text = new_text;
361         file_data.is_overlayed = is_overlayed;
362     }
363
364     fn remove_file(&mut self, file: VfsFile) {
365         // FIXME: use arena with removal
366         self.files[file].text = Default::default();
367         self.files[file].path = Default::default();
368         let root = self.files[file].root;
369         let removed = self.root2files.get_mut(root).unwrap().remove(&file);
370         assert!(removed);
371     }
372
373     fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
374         let (root, path) = self.roots.find(&path)?;
375         let file = self.find_file(root, &path);
376         Some((root, path, file))
377     }
378
379     fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
380         self.root2files[root].iter().map(|&it| it).find(|&file| self.files[file].path == path)
381     }
382 }
383
384 #[derive(Debug, Clone)]
385 pub enum VfsChange {
386     AddRoot { root: VfsRoot, files: Vec<(VfsFile, RelativePathBuf, Arc<String>)> },
387     AddFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf, text: Arc<String> },
388     RemoveFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf },
389     ChangeFile { file: VfsFile, text: Arc<String> },
390 }