aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_vfs/src/lib.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-02-18 13:43:30 +0000
committerAleksey Kladov <[email protected]>2019-02-18 13:43:48 +0000
commita6897a837c2f633bdc88b87c614db227aab3b1ad (patch)
tree2c03b6df907077945f02f52ce6c5ca131b69ee93 /crates/ra_vfs/src/lib.rs
parent1bdd935e9144d94fdbfa46d6fe47778379bceacf (diff)
remove local-vfs
Diffstat (limited to 'crates/ra_vfs/src/lib.rs')
-rw-r--r--crates/ra_vfs/src/lib.rs296
1 files changed, 0 insertions, 296 deletions
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs
deleted file mode 100644
index 808c138df..000000000
--- a/crates/ra_vfs/src/lib.rs
+++ /dev/null
@@ -1,296 +0,0 @@
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.
18mod roots;
19mod io;
20
21use std::{
22 fmt, fs, mem,
23 path::{Path, PathBuf},
24 sync::Arc,
25};
26
27use crossbeam_channel::Receiver;
28use relative_path::{RelativePath, RelativePathBuf};
29use rustc_hash::{FxHashMap, FxHashSet};
30
31use crate::{
32 io::{TaskResult, Worker},
33 roots::Roots,
34};
35
36pub use crate::roots::VfsRoot;
37
38/// Opaque wrapper around file-system event.
39///
40/// Calling code is expected to just pass `VfsTask` to `handle_task` method. It
41/// is exposed as a public API so that the caller can plug vfs events into the
42/// main event loop and be notified when changes happen.
43pub struct VfsTask(TaskResult);
44
45impl fmt::Debug for VfsTask {
46 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47 f.write_str("VfsTask { ... }")
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
52pub struct VfsFile(pub u32);
53
54struct VfsFileData {
55 root: VfsRoot,
56 path: RelativePathBuf,
57 is_overlayed: bool,
58 text: Arc<String>,
59}
60
61pub struct Vfs {
62 roots: Arc<Roots>,
63 files: Vec<VfsFileData>,
64 root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
65 pending_changes: Vec<VfsChange>,
66 worker: Worker,
67}
68
69impl fmt::Debug for Vfs {
70 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71 f.debug_struct("Vfs")
72 .field("n_roots", &self.roots.len())
73 .field("n_files", &self.files.len())
74 .field("n_pending_changes", &self.pending_changes.len())
75 .finish()
76 }
77}
78
79#[derive(Debug, Clone)]
80pub enum VfsChange {
81 AddRoot { root: VfsRoot, files: Vec<(VfsFile, RelativePathBuf, Arc<String>)> },
82 AddFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf, text: Arc<String> },
83 RemoveFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf },
84 ChangeFile { file: VfsFile, text: Arc<String> },
85}
86
87impl Vfs {
88 pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
89 let roots = Arc::new(Roots::new(roots));
90 let worker = io::start(Arc::clone(&roots));
91 let mut root2files = FxHashMap::default();
92
93 for root in roots.iter() {
94 root2files.insert(root, Default::default());
95 worker.sender.send(io::Task::AddRoot { root }).unwrap();
96 }
97 let res = Vfs { roots, files: Vec::new(), root2files, worker, pending_changes: Vec::new() };
98 let vfs_roots = res.roots.iter().collect();
99 (res, vfs_roots)
100 }
101
102 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
103 self.roots.path(root).to_path_buf()
104 }
105
106 pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
107 if let Some((_root, _path, Some(file))) = self.find_root(path) {
108 return Some(file);
109 }
110 None
111 }
112
113 pub fn file2path(&self, file: VfsFile) -> PathBuf {
114 let rel_path = &self.file(file).path;
115 let root_path = &self.roots.path(self.file(file).root);
116 rel_path.to_path(root_path)
117 }
118
119 pub fn n_roots(&self) -> usize {
120 self.roots.len()
121 }
122
123 pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
124 if let Some((root, rel_path, file)) = self.find_root(path) {
125 return if let Some(file) = file {
126 Some(file)
127 } else {
128 let text = fs::read_to_string(path).unwrap_or_default();
129 let text = Arc::new(text);
130 let file = self.raw_add_file(root, rel_path.clone(), Arc::clone(&text), false);
131 let change = VfsChange::AddFile { file, text, root, path: rel_path };
132 self.pending_changes.push(change);
133 Some(file)
134 };
135 }
136 None
137 }
138
139 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
140 let (root, rel_path, file) = self.find_root(path)?;
141 if let Some(file) = file {
142 self.change_file_event(file, text, true);
143 Some(file)
144 } else {
145 self.add_file_event(root, rel_path, text, true)
146 }
147 }
148
149 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
150 if let Some((_root, _path, file)) = self.find_root(path) {
151 let file = file.expect("can't change a file which wasn't added");
152 self.change_file_event(file, new_text, true);
153 }
154 }
155
156 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
157 let (root, rel_path, file) = self.find_root(path)?;
158 let file = file.expect("can't remove a file which wasn't added");
159 let full_path = rel_path.to_path(&self.roots.path(root));
160 if let Ok(text) = fs::read_to_string(&full_path) {
161 self.change_file_event(file, text, false);
162 } else {
163 self.remove_file_event(root, rel_path, file);
164 }
165 Some(file)
166 }
167
168 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
169 mem::replace(&mut self.pending_changes, Vec::new())
170 }
171
172 pub fn task_receiver(&self) -> &Receiver<VfsTask> {
173 &self.worker.receiver
174 }
175
176 pub fn handle_task(&mut self, task: VfsTask) {
177 match task.0 {
178 TaskResult::BulkLoadRoot { root, files } => {
179 let mut cur_files = Vec::new();
180 // While we were scanning the root in the background, a file might have
181 // been open in the editor, so we need to account for that.
182 let existing = self.root2files[&root]
183 .iter()
184 .map(|&file| (self.file(file).path.clone(), file))
185 .collect::<FxHashMap<_, _>>();
186 for (path, text) in files {
187 if let Some(&file) = existing.get(&path) {
188 let text = Arc::clone(&self.file(file).text);
189 cur_files.push((file, path, text));
190 continue;
191 }
192 let text = Arc::new(text);
193 let file = self.raw_add_file(root, path.clone(), Arc::clone(&text), false);
194 cur_files.push((file, path, text));
195 }
196
197 let change = VfsChange::AddRoot { root, files: cur_files };
198 self.pending_changes.push(change);
199 }
200 TaskResult::SingleFile { root, path, text } => {
201 let existing_file = self.find_file(root, &path);
202 if existing_file.map(|file| self.file(file).is_overlayed) == Some(true) {
203 return;
204 }
205 match (existing_file, text) {
206 (Some(file), None) => {
207 self.remove_file_event(root, path, file);
208 }
209 (None, Some(text)) => {
210 self.add_file_event(root, path, text, false);
211 }
212 (Some(file), Some(text)) => {
213 self.change_file_event(file, text, false);
214 }
215 (None, None) => (),
216 }
217 }
218 }
219 }
220
221 // *_event calls change the state of VFS and push a change onto pending
222 // changes array.
223
224 fn add_file_event(
225 &mut self,
226 root: VfsRoot,
227 path: RelativePathBuf,
228 text: String,
229 is_overlay: bool,
230 ) -> Option<VfsFile> {
231 let text = Arc::new(text);
232 let file = self.raw_add_file(root, path.clone(), text.clone(), is_overlay);
233 self.pending_changes.push(VfsChange::AddFile { file, root, path, text });
234 Some(file)
235 }
236
237 fn change_file_event(&mut self, file: VfsFile, text: String, is_overlay: bool) {
238 let text = Arc::new(text);
239 self.raw_change_file(file, text.clone(), is_overlay);
240 self.pending_changes.push(VfsChange::ChangeFile { file, text });
241 }
242
243 fn remove_file_event(&mut self, root: VfsRoot, path: RelativePathBuf, file: VfsFile) {
244 self.raw_remove_file(file);
245 self.pending_changes.push(VfsChange::RemoveFile { root, path, file });
246 }
247
248 // raw_* calls change the state of VFS, but **do not** emit events.
249
250 fn raw_add_file(
251 &mut self,
252 root: VfsRoot,
253 path: RelativePathBuf,
254 text: Arc<String>,
255 is_overlayed: bool,
256 ) -> VfsFile {
257 let data = VfsFileData { root, path, text, is_overlayed };
258 let file = VfsFile(self.files.len() as u32);
259 self.files.push(data);
260 self.root2files.get_mut(&root).unwrap().insert(file);
261 file
262 }
263
264 fn raw_change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
265 let mut file_data = &mut self.file_mut(file);
266 file_data.text = new_text;
267 file_data.is_overlayed = is_overlayed;
268 }
269
270 fn raw_remove_file(&mut self, file: VfsFile) {
271 // FIXME: use arena with removal
272 self.file_mut(file).text = Default::default();
273 self.file_mut(file).path = Default::default();
274 let root = self.file(file).root;
275 let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
276 assert!(removed);
277 }
278
279 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
280 let (root, path) = self.roots.find(&path)?;
281 let file = self.find_file(root, &path);
282 Some((root, path, file))
283 }
284
285 fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
286 self.root2files[&root].iter().map(|&it| it).find(|&file| self.file(file).path == path)
287 }
288
289 fn file(&self, file: VfsFile) -> &VfsFileData {
290 &self.files[file.0 as usize]
291 }
292
293 fn file_mut(&mut self, file: VfsFile) -> &mut VfsFileData {
294 &mut self.files[file.0 as usize]
295 }
296}