]> git.lizzy.rs Git - rust.git/blob - crates/ra_vfs/src/lib.rs
doc comment
[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 //! component which does this.
6 //! need to get it into the memory in the first place somehow. VFS is the
7 //!
8 //! It also is responsible for watching the disk for changes, and for merging
9 //! editor state (modified, unsaved files) with disk state.
10 //!
11 //! VFS is based on a concept of roots: a set of directories on the file system
12 //! whihc are watched for changes. Typically, there will be a root for each
13 //! Cargo package.
14 mod arena;
15 mod io;
16
17 use std::{
18     mem,
19     thread,
20     cmp::Reverse,
21     path::{Path, PathBuf},
22     ffi::OsStr,
23     sync::Arc,
24     fs,
25 };
26
27 use rustc_hash::{FxHashMap, FxHashSet};
28 use relative_path::RelativePathBuf;
29 use crossbeam_channel::Receiver;
30 use walkdir::DirEntry;
31 use thread_worker::{WorkerHandle};
32
33 use crate::{
34     arena::{ArenaId, Arena},
35 };
36
37 /// `RootFilter` is a predicate that checks if a file can belong to a root. If
38 /// several filters match a file (nested dirs), the most nested one wins.
39 struct RootFilter {
40     root: PathBuf,
41     file_filter: fn(&Path) -> bool,
42 }
43
44 impl RootFilter {
45     fn new(root: PathBuf) -> RootFilter {
46         RootFilter {
47             root,
48             file_filter: has_rs_extension,
49         }
50     }
51     /// Check if this root can contain `path`. NB: even if this returns
52     /// true, the `path` might actually be conained in some nested root.
53     fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
54         if !(self.file_filter)(path) {
55             return None;
56         }
57         if !(path.starts_with(&self.root)) {
58             return None;
59         }
60         let path = path.strip_prefix(&self.root).unwrap();
61         let path = RelativePathBuf::from_path(path).unwrap();
62         Some(path)
63     }
64 }
65
66 fn has_rs_extension(p: &Path) -> bool {
67     p.extension() == Some(OsStr::new("rs"))
68 }
69
70 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
71 pub struct VfsRoot(u32);
72
73 impl ArenaId for VfsRoot {
74     fn from_u32(idx: u32) -> VfsRoot {
75         VfsRoot(idx)
76     }
77     fn to_u32(self) -> u32 {
78         self.0
79     }
80 }
81
82 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
83 pub struct VfsFile(u32);
84
85 impl ArenaId for VfsFile {
86     fn from_u32(idx: u32) -> VfsFile {
87         VfsFile(idx)
88     }
89     fn to_u32(self) -> u32 {
90         self.0
91     }
92 }
93
94 struct VfsFileData {
95     root: VfsRoot,
96     path: RelativePathBuf,
97     text: Arc<String>,
98 }
99
100 pub struct Vfs {
101     roots: Arena<VfsRoot, RootFilter>,
102     files: Arena<VfsFile, VfsFileData>,
103     root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
104     pending_changes: Vec<VfsChange>,
105     worker: io::Worker,
106     worker_handle: WorkerHandle,
107 }
108
109 impl Vfs {
110     pub fn new(mut roots: Vec<PathBuf>) -> Vfs {
111         let (worker, worker_handle) = io::start();
112
113         let mut res = Vfs {
114             roots: Arena::default(),
115             files: Arena::default(),
116             root2files: FxHashMap::default(),
117             worker,
118             worker_handle,
119             pending_changes: Vec::new(),
120         };
121
122         // A hack to make nesting work.
123         roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
124         for (i, path) in roots.iter().enumerate() {
125             let root = res.roots.alloc(RootFilter::new(path.clone()));
126             let nested = roots[..i]
127                 .iter()
128                 .filter(|it| it.starts_with(path))
129                 .map(|it| it.clone())
130                 .collect::<Vec<_>>();
131             let filter = move |entry: &DirEntry| {
132                 if entry.file_type().is_file() {
133                     has_rs_extension(entry.path())
134                 } else {
135                     nested.iter().all(|it| it != entry.path())
136                 }
137             };
138             let task = io::Task {
139                 root,
140                 path: path.clone(),
141                 filter: Box::new(filter),
142             };
143             res.worker.inp.send(task);
144         }
145         res
146     }
147
148     pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
149         &self.worker.out
150     }
151
152     pub fn handle_task(&mut self, task: io::TaskResult) {
153         let mut files = Vec::new();
154         for (path, text) in task.files {
155             let text = Arc::new(text);
156             let file = self.add_file(task.root, path.clone(), Arc::clone(&text));
157             files.push((file, path, text));
158         }
159         let change = VfsChange::AddRoot {
160             root: task.root,
161             files,
162         };
163         self.pending_changes.push(change);
164     }
165
166     pub fn add_file_overlay(&mut self, path: &Path, text: String) {
167         if let Some((root, path, file)) = self.find_root(path) {
168             let text = Arc::new(text);
169             let change = if let Some(file) = file {
170                 self.change_file(file, Arc::clone(&text));
171                 VfsChange::ChangeFile { file, text }
172             } else {
173                 let file = self.add_file(root, path.clone(), Arc::clone(&text));
174                 VfsChange::AddFile {
175                     file,
176                     text,
177                     root,
178                     path,
179                 }
180             };
181             self.pending_changes.push(change);
182         }
183     }
184
185     pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
186         if let Some((_root, _path, file)) = self.find_root(path) {
187             let file = file.expect("can't change a file which wasn't added");
188             let text = Arc::new(new_text);
189             self.change_file(file, Arc::clone(&text));
190             let change = VfsChange::ChangeFile { file, text };
191             self.pending_changes.push(change);
192         }
193     }
194
195     pub fn remove_file_overlay(&mut self, path: &Path) {
196         if let Some((root, path, file)) = self.find_root(path) {
197             let file = file.expect("can't remove a file which wasn't added");
198             let full_path = path.to_path(&self.roots[root].root);
199             let change = if let Ok(text) = fs::read_to_string(&full_path) {
200                 let text = Arc::new(text);
201                 self.change_file(file, Arc::clone(&text));
202                 VfsChange::ChangeFile { file, text }
203             } else {
204                 self.remove_file(file);
205                 VfsChange::RemoveFile { root, file, path }
206             };
207             self.pending_changes.push(change);
208         }
209     }
210
211     pub fn commit_changes(&mut self) -> Vec<VfsChange> {
212         mem::replace(&mut self.pending_changes, Vec::new())
213     }
214
215     /// Sutdown the VFS and terminate the background watching thread.
216     pub fn shutdown(self) -> thread::Result<()> {
217         let _ = self.worker.shutdown();
218         self.worker_handle.shutdown()
219     }
220
221     fn add_file(&mut self, root: VfsRoot, path: RelativePathBuf, text: Arc<String>) -> VfsFile {
222         let data = VfsFileData { root, path, text };
223         let file = self.files.alloc(data);
224         self.root2files
225             .entry(root)
226             .or_insert_with(FxHashSet::default)
227             .insert(file);
228         file
229     }
230
231     fn change_file(&mut self, file: VfsFile, new_text: Arc<String>) {
232         self.files[file].text = new_text;
233     }
234
235     fn remove_file(&mut self, file: VfsFile) {
236         //FIXME: use arena with removal
237         self.files[file].text = Default::default();
238         self.files[file].path = Default::default();
239         let root = self.files[file].root;
240         let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
241         assert!(removed);
242     }
243
244     fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
245         let (root, path) = self
246             .roots
247             .iter()
248             .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?;
249         let file = self.root2files[&root]
250             .iter()
251             .map(|&it| it)
252             .find(|&file| self.files[file].path == path);
253         Some((root, path, file))
254     }
255 }
256
257 #[derive(Debug, Clone)]
258 pub enum VfsChange {
259     AddRoot {
260         root: VfsRoot,
261         files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
262     },
263     AddFile {
264         root: VfsRoot,
265         file: VfsFile,
266         path: RelativePathBuf,
267         text: Arc<String>,
268     },
269     RemoveFile {
270         root: VfsRoot,
271         file: VfsFile,
272         path: RelativePathBuf,
273     },
274     ChangeFile {
275         file: VfsFile,
276         text: Arc<String>,
277     },
278 }