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