]> git.lizzy.rs Git - rust.git/blob - crates/ra_vfs/src/lib.rs
initial Watcher impl
[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 mod watcher;
18
19 use std::{
20     cmp::Reverse,
21     ffi::OsStr,
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};
30 use relative_path::RelativePathBuf;
31 use rustc_hash::{FxHashMap, FxHashSet};
32 use thread_worker::WorkerHandle;
33 use walkdir::DirEntry;
34
35 pub use crate::io::TaskResult as VfsTask;
36 pub use crate::watcher::{Watcher, WatcherChange};
37
38 /// `RootFilter` is a predicate that checks if a file can belong to a root. If
39 /// several filters match a file (nested dirs), the most nested one wins.
40 struct RootFilter {
41     root: PathBuf,
42     file_filter: fn(&Path) -> bool,
43 }
44
45 impl RootFilter {
46     fn new(root: PathBuf) -> RootFilter {
47         RootFilter {
48             root,
49             file_filter: has_rs_extension,
50         }
51     }
52     /// Check if this root can contain `path`. NB: even if this returns
53     /// true, the `path` might actually be conained in some nested root.
54     fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
55         if !(self.file_filter)(path) {
56             return None;
57         }
58         let path = path.strip_prefix(&self.root).ok()?;
59         RelativePathBuf::from_path(path).ok()
60     }
61 }
62
63 fn has_rs_extension(p: &Path) -> bool {
64     p.extension() == Some(OsStr::new("rs"))
65 }
66
67 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
68 pub struct VfsRoot(pub RawId);
69 impl_arena_id!(VfsRoot);
70
71 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
72 pub struct VfsFile(pub RawId);
73 impl_arena_id!(VfsFile);
74
75 struct VfsFileData {
76     root: VfsRoot,
77     path: RelativePathBuf,
78     text: Arc<String>,
79 }
80
81 pub struct Vfs {
82     roots: Arena<VfsRoot, RootFilter>,
83     files: Arena<VfsFile, VfsFileData>,
84     root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
85     pending_changes: Vec<VfsChange>,
86     worker: io::Worker,
87     worker_handle: WorkerHandle,
88     watcher: Watcher,
89 }
90
91 impl fmt::Debug for Vfs {
92     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93         f.write_str("Vfs { ... }")
94     }
95 }
96
97 impl Vfs {
98     pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
99         let (worker, worker_handle) = io::start();
100
101         let watcher = Watcher::new().unwrap(); // TODO return Result?
102
103         let mut res = Vfs {
104             roots: Arena::default(),
105             files: Arena::default(),
106             root2files: FxHashMap::default(),
107             worker,
108             worker_handle,
109             watcher,
110             pending_changes: Vec::new(),
111         };
112
113         // A hack to make nesting work.
114         roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
115         for (i, path) in roots.iter().enumerate() {
116             let root = res.roots.alloc(RootFilter::new(path.clone()));
117             res.root2files.insert(root, Default::default());
118             let nested = roots[..i]
119                 .iter()
120                 .filter(|it| it.starts_with(path))
121                 .map(|it| it.clone())
122                 .collect::<Vec<_>>();
123             let filter = move |entry: &DirEntry| {
124                 if entry.file_type().is_file() {
125                     has_rs_extension(entry.path())
126                 } else {
127                     nested.iter().all(|it| it != entry.path())
128                 }
129             };
130             let task = io::Task {
131                 root,
132                 path: path.clone(),
133                 filter: Box::new(filter),
134             };
135             res.worker.inp.send(task).unwrap();
136             res.watcher.watch(path).unwrap();
137         }
138         let roots = res.roots.iter().map(|(id, _)| id).collect();
139         (res, roots)
140     }
141
142     pub fn root2path(&self, root: VfsRoot) -> PathBuf {
143         self.roots[root].root.clone()
144     }
145
146     pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
147         if let Some((_root, _path, Some(file))) = self.find_root(path) {
148             return Some(file);
149         }
150         None
151     }
152
153     pub fn file2path(&self, file: VfsFile) -> PathBuf {
154         let rel_path = &self.files[file].path;
155         let root_path = &self.roots[self.files[file].root].root;
156         rel_path.to_path(root_path)
157     }
158
159     pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
160         if let Some((_root, _path, Some(file))) = self.find_root(path) {
161             return Some(file);
162         }
163         None
164     }
165
166     pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
167         if let Some((root, rel_path, file)) = self.find_root(path) {
168             return if let Some(file) = file {
169                 Some(file)
170             } else {
171                 let text = fs::read_to_string(path).unwrap_or_default();
172                 let text = Arc::new(text);
173                 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text));
174                 let change = VfsChange::AddFile {
175                     file,
176                     text,
177                     root,
178                     path: rel_path,
179                 };
180                 self.pending_changes.push(change);
181                 Some(file)
182             };
183         }
184         None
185     }
186
187     pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
188         &self.worker.out
189     }
190
191     pub fn change_receiver(&self) -> &Receiver<WatcherChange> {
192         &self.watcher.change_receiver()
193     }
194
195     pub fn handle_task(&mut self, task: io::TaskResult) {
196         let mut files = Vec::new();
197         // While we were scanning the root in the backgound, a file might have
198         // been open in the editor, so we need to account for that.
199         let exising = self.root2files[&task.root]
200             .iter()
201             .map(|&file| (self.files[file].path.clone(), file))
202             .collect::<FxHashMap<_, _>>();
203         for (path, text) in task.files {
204             if let Some(&file) = exising.get(&path) {
205                 let text = Arc::clone(&self.files[file].text);
206                 files.push((file, path, text));
207                 continue;
208             }
209             let text = Arc::new(text);
210             let file = self.add_file(task.root, path.clone(), Arc::clone(&text));
211             files.push((file, path, text));
212         }
213
214         let change = VfsChange::AddRoot {
215             root: task.root,
216             files,
217         };
218         self.pending_changes.push(change);
219     }
220
221     pub fn handle_change(&mut self, change: WatcherChange) {
222         match change {
223             WatcherChange::Create(path) => {
224                 self.add_file_overlay(&path, None);
225             }
226             WatcherChange::Remove(path) => {
227                 self.remove_file_overlay(&path);
228             }
229             WatcherChange::Rename(src, dst) => {
230                 self.remove_file_overlay(&src);
231                 self.add_file_overlay(&dst, None);
232             }
233             WatcherChange::Write(path) => {
234                 self.change_file_overlay(&path, None);
235             }
236         }
237     }
238
239     pub fn add_file_overlay(&mut self, path: &Path, text: Option<String>) -> Option<VfsFile> {
240         let mut res = None;
241         if let Some((root, rel_path, file)) = self.find_root(path) {
242             let text = text.unwrap_or_else(|| fs::read_to_string(&path).unwrap_or_default());
243             let text = Arc::new(text);
244             let change = if let Some(file) = file {
245                 res = Some(file);
246                 self.change_file(file, Arc::clone(&text));
247                 VfsChange::ChangeFile { file, text }
248             } else {
249                 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text));
250                 res = Some(file);
251                 VfsChange::AddFile {
252                     file,
253                     text,
254                     root,
255                     path: rel_path,
256                 }
257             };
258             self.pending_changes.push(change);
259         }
260         res
261     }
262
263     pub fn change_file_overlay(&mut self, path: &Path, new_text: Option<String>) {
264         if let Some((_root, _path, file)) = self.find_root(path) {
265             let new_text =
266                 new_text.unwrap_or_else(|| fs::read_to_string(&path).unwrap_or_default());
267             let file = file.expect("can't change a file which wasn't added");
268             let text = Arc::new(new_text);
269             self.change_file(file, Arc::clone(&text));
270             let change = VfsChange::ChangeFile { file, text };
271             self.pending_changes.push(change);
272         }
273     }
274
275     pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
276         let mut res = None;
277         if let Some((root, path, file)) = self.find_root(path) {
278             let file = file.expect("can't remove a file which wasn't added");
279             res = Some(file);
280             let full_path = path.to_path(&self.roots[root].root);
281             let change = if let Ok(text) = fs::read_to_string(&full_path) {
282                 let text = Arc::new(text);
283                 self.change_file(file, Arc::clone(&text));
284                 VfsChange::ChangeFile { file, text }
285             } else {
286                 self.remove_file(file);
287                 VfsChange::RemoveFile { root, file, path }
288             };
289             self.pending_changes.push(change);
290         }
291         res
292     }
293
294     pub fn commit_changes(&mut self) -> Vec<VfsChange> {
295         mem::replace(&mut self.pending_changes, Vec::new())
296     }
297
298     /// Sutdown the VFS and terminate the background watching thread.
299     pub fn shutdown(self) -> thread::Result<()> {
300         let _ = self.watcher.shutdown();
301         let _ = self.worker.shutdown();
302         self.worker_handle.shutdown()
303     }
304
305     fn add_file(&mut self, root: VfsRoot, path: RelativePathBuf, text: Arc<String>) -> VfsFile {
306         let data = VfsFileData { root, path, text };
307         let file = self.files.alloc(data);
308         self.root2files.get_mut(&root).unwrap().insert(file);
309         file
310     }
311
312     fn change_file(&mut self, file: VfsFile, new_text: Arc<String>) {
313         self.files[file].text = new_text;
314     }
315
316     fn remove_file(&mut self, file: VfsFile) {
317         //FIXME: use arena with removal
318         self.files[file].text = Default::default();
319         self.files[file].path = Default::default();
320         let root = self.files[file].root;
321         let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
322         assert!(removed);
323     }
324
325     fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
326         let (root, path) = self
327             .roots
328             .iter()
329             .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?;
330         let file = self.root2files[&root]
331             .iter()
332             .map(|&it| it)
333             .find(|&file| self.files[file].path == path);
334         Some((root, path, file))
335     }
336 }
337
338 #[derive(Debug, Clone)]
339 pub enum VfsChange {
340     AddRoot {
341         root: VfsRoot,
342         files: Vec<(VfsFile, RelativePathBuf, Arc<String>)>,
343     },
344     AddFile {
345         root: VfsRoot,
346         file: VfsFile,
347         path: RelativePathBuf,
348         text: Arc<String>,
349     },
350     RemoveFile {
351         root: VfsRoot,
352         file: VfsFile,
353         path: RelativePathBuf,
354     },
355     ChangeFile {
356         file: VfsFile,
357         text: Arc<String>,
358     },
359 }