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