diff options
Diffstat (limited to 'crates/ra_vfs/src/lib.rs')
-rw-r--r-- | crates/ra_vfs/src/lib.rs | 248 |
1 files changed, 72 insertions, 176 deletions
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index cfdc1275f..7f555a3c0 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs | |||
@@ -15,10 +15,10 @@ | |||
15 | //! VFS is based on a concept of roots: a set of directories on the file system | 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 | 16 | //! which are watched for changes. Typically, there will be a root for each |
17 | //! Cargo package. | 17 | //! Cargo package. |
18 | mod roots; | ||
18 | mod io; | 19 | mod io; |
19 | 20 | ||
20 | use std::{ | 21 | use std::{ |
21 | cmp::Reverse, | ||
22 | fmt, fs, mem, | 22 | fmt, fs, mem, |
23 | path::{Path, PathBuf}, | 23 | path::{Path, PathBuf}, |
24 | sync::Arc, | 24 | sync::Arc, |
@@ -26,106 +26,18 @@ use std::{ | |||
26 | 26 | ||
27 | use crossbeam_channel::Receiver; | 27 | use crossbeam_channel::Receiver; |
28 | use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap}; | 28 | use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap}; |
29 | use relative_path::{Component, RelativePath, RelativePathBuf}; | 29 | use relative_path::{RelativePath, RelativePathBuf}; |
30 | use rustc_hash::{FxHashMap, FxHashSet}; | 30 | use rustc_hash::{FxHashMap, FxHashSet}; |
31 | 31 | ||
32 | pub use crate::io::TaskResult as VfsTask; | 32 | use crate::{ |
33 | use io::{TaskResult, Worker}; | 33 | io::{TaskResult, Worker}, |
34 | 34 | roots::Roots, | |
35 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | 35 | }; |
36 | pub struct VfsRoot(pub RawId); | ||
37 | impl_arena_id!(VfsRoot); | ||
38 | |||
39 | /// Describes the contents of a single source root. | ||
40 | /// | ||
41 | /// `RootConfig` can be thought of as a glob pattern like `src/**.rs` which | ||
42 | /// specifies the source root or as a function which takes a `PathBuf` and | ||
43 | /// returns `true` iff path belongs to the source root | ||
44 | pub(crate) struct RootConfig { | ||
45 | root: PathBuf, | ||
46 | // result of `root.canonicalize()` if that differs from `root`; `None` otherwise. | ||
47 | canonical_root: Option<PathBuf>, | ||
48 | excluded_dirs: Vec<PathBuf>, | ||
49 | } | ||
50 | |||
51 | pub(crate) struct Roots { | ||
52 | roots: Arena<VfsRoot, Arc<RootConfig>>, | ||
53 | } | ||
54 | |||
55 | impl std::ops::Deref for Roots { | ||
56 | type Target = Arena<VfsRoot, Arc<RootConfig>>; | ||
57 | fn deref(&self) -> &Self::Target { | ||
58 | &self.roots | ||
59 | } | ||
60 | } | ||
61 | |||
62 | impl RootConfig { | ||
63 | fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootConfig { | ||
64 | let mut canonical_root = root.canonicalize().ok(); | ||
65 | if Some(&root) == canonical_root.as_ref() { | ||
66 | canonical_root = None; | ||
67 | } | ||
68 | RootConfig { root, canonical_root, excluded_dirs } | ||
69 | } | ||
70 | /// Checks if root contains a path and returns a root-relative path. | ||
71 | pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> { | ||
72 | // First, check excluded dirs | ||
73 | if self.excluded_dirs.iter().any(|it| path.starts_with(it)) { | ||
74 | return None; | ||
75 | } | ||
76 | let rel_path = path | ||
77 | .strip_prefix(&self.root) | ||
78 | .or_else(|err_payload| { | ||
79 | self.canonical_root | ||
80 | .as_ref() | ||
81 | .map_or(Err(err_payload), |canonical_root| path.strip_prefix(canonical_root)) | ||
82 | }) | ||
83 | .ok()?; | ||
84 | let rel_path = RelativePathBuf::from_path(rel_path).ok()?; | ||
85 | |||
86 | // Ignore some common directories. | ||
87 | // | ||
88 | // FIXME: don't hard-code, specify at source-root creation time using | ||
89 | // gitignore | ||
90 | for (i, c) in rel_path.components().enumerate() { | ||
91 | if let Component::Normal(c) = c { | ||
92 | if (i == 0 && c == "target") || c == ".git" || c == "node_modules" { | ||
93 | return None; | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | |||
98 | if path.is_file() && rel_path.extension() != Some("rs") { | ||
99 | return None; | ||
100 | } | ||
101 | |||
102 | Some(rel_path) | ||
103 | } | ||
104 | } | ||
105 | 36 | ||
106 | impl Roots { | 37 | pub use crate::{ |
107 | pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots { | 38 | io::TaskResult as VfsTask, |
108 | let mut roots = Arena::default(); | 39 | roots::VfsRoot, |
109 | // A hack to make nesting work. | 40 | }; |
110 | paths.sort_by_key(|it| Reverse(it.as_os_str().len())); | ||
111 | paths.dedup(); | ||
112 | for (i, path) in paths.iter().enumerate() { | ||
113 | let nested_roots = paths[..i] | ||
114 | .iter() | ||
115 | .filter(|it| it.starts_with(path)) | ||
116 | .map(|it| it.clone()) | ||
117 | .collect::<Vec<_>>(); | ||
118 | |||
119 | let config = Arc::new(RootConfig::new(path.clone(), nested_roots)); | ||
120 | |||
121 | roots.alloc(config.clone()); | ||
122 | } | ||
123 | Roots { roots } | ||
124 | } | ||
125 | pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> { | ||
126 | self.roots.iter().find_map(|(root, data)| data.contains(path).map(|it| (root, it))) | ||
127 | } | ||
128 | } | ||
129 | 41 | ||
130 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | 42 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
131 | pub struct VfsFile(pub RawId); | 43 | pub struct VfsFile(pub RawId); |
@@ -162,18 +74,18 @@ impl Vfs { | |||
162 | let worker = io::start(Arc::clone(&roots)); | 74 | let worker = io::start(Arc::clone(&roots)); |
163 | let mut root2files = ArenaMap::default(); | 75 | let mut root2files = ArenaMap::default(); |
164 | 76 | ||
165 | for (root, config) in roots.iter() { | 77 | for root in roots.iter() { |
166 | root2files.insert(root, Default::default()); | 78 | root2files.insert(root, Default::default()); |
167 | worker.sender().send(io::Task::AddRoot { root, config: Arc::clone(config) }).unwrap(); | 79 | worker.sender().send(io::Task::AddRoot { root }).unwrap(); |
168 | } | 80 | } |
169 | let res = | 81 | let res = |
170 | Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() }; | 82 | Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() }; |
171 | let vfs_roots = res.roots.iter().map(|(id, _)| id).collect(); | 83 | let vfs_roots = res.roots.iter().collect(); |
172 | (res, vfs_roots) | 84 | (res, vfs_roots) |
173 | } | 85 | } |
174 | 86 | ||
175 | pub fn root2path(&self, root: VfsRoot) -> PathBuf { | 87 | pub fn root2path(&self, root: VfsRoot) -> PathBuf { |
176 | self.roots[root].root.clone() | 88 | self.roots.path(root).to_path_buf() |
177 | } | 89 | } |
178 | 90 | ||
179 | pub fn path2file(&self, path: &Path) -> Option<VfsFile> { | 91 | pub fn path2file(&self, path: &Path) -> Option<VfsFile> { |
@@ -185,18 +97,11 @@ impl Vfs { | |||
185 | 97 | ||
186 | pub fn file2path(&self, file: VfsFile) -> PathBuf { | 98 | pub fn file2path(&self, file: VfsFile) -> PathBuf { |
187 | let rel_path = &self.files[file].path; | 99 | let rel_path = &self.files[file].path; |
188 | let root_path = &self.roots[self.files[file].root].root; | 100 | let root_path = &self.roots.path(self.files[file].root); |
189 | rel_path.to_path(root_path) | 101 | rel_path.to_path(root_path) |
190 | } | 102 | } |
191 | 103 | ||
192 | pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> { | 104 | pub fn n_roots(&self) -> usize { |
193 | if let Some((_root, _path, Some(file))) = self.find_root(path) { | ||
194 | return Some(file); | ||
195 | } | ||
196 | None | ||
197 | } | ||
198 | |||
199 | pub fn num_roots(&self) -> usize { | ||
200 | self.roots.len() | 105 | self.roots.len() |
201 | } | 106 | } |
202 | 107 | ||
@@ -207,7 +112,7 @@ impl Vfs { | |||
207 | } else { | 112 | } else { |
208 | let text = fs::read_to_string(path).unwrap_or_default(); | 113 | let text = fs::read_to_string(path).unwrap_or_default(); |
209 | let text = Arc::new(text); | 114 | let text = Arc::new(text); |
210 | let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false); | 115 | let file = self.raw_add_file(root, rel_path.clone(), Arc::clone(&text), false); |
211 | let change = VfsChange::AddFile { file, text, root, path: rel_path }; | 116 | let change = VfsChange::AddFile { file, text, root, path: rel_path }; |
212 | self.pending_changes.push(change); | 117 | self.pending_changes.push(change); |
213 | Some(file) | 118 | Some(file) |
@@ -216,6 +121,39 @@ impl Vfs { | |||
216 | None | 121 | None |
217 | } | 122 | } |
218 | 123 | ||
124 | pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> { | ||
125 | let (root, rel_path, file) = self.find_root(path)?; | ||
126 | if let Some(file) = file { | ||
127 | self.change_file_event(file, text, true); | ||
128 | Some(file) | ||
129 | } else { | ||
130 | self.add_file_event(root, rel_path, text, true) | ||
131 | } | ||
132 | } | ||
133 | |||
134 | pub fn change_file_overlay(&mut self, path: &Path, new_text: String) { | ||
135 | if let Some((_root, _path, file)) = self.find_root(path) { | ||
136 | let file = file.expect("can't change a file which wasn't added"); | ||
137 | self.change_file_event(file, new_text, true); | ||
138 | } | ||
139 | } | ||
140 | |||
141 | pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> { | ||
142 | let (root, rel_path, file) = self.find_root(path)?; | ||
143 | let file = file.expect("can't remove a file which wasn't added"); | ||
144 | let full_path = rel_path.to_path(&self.roots.path(root)); | ||
145 | if let Ok(text) = fs::read_to_string(&full_path) { | ||
146 | self.change_file_event(file, text, false); | ||
147 | } else { | ||
148 | self.remove_file_event(root, rel_path, file); | ||
149 | } | ||
150 | Some(file) | ||
151 | } | ||
152 | |||
153 | pub fn commit_changes(&mut self) -> Vec<VfsChange> { | ||
154 | mem::replace(&mut self.pending_changes, Vec::new()) | ||
155 | } | ||
156 | |||
219 | pub fn task_receiver(&self) -> &Receiver<io::TaskResult> { | 157 | pub fn task_receiver(&self) -> &Receiver<io::TaskResult> { |
220 | self.worker.receiver() | 158 | self.worker.receiver() |
221 | } | 159 | } |
@@ -237,7 +175,7 @@ impl Vfs { | |||
237 | continue; | 175 | continue; |
238 | } | 176 | } |
239 | let text = Arc::new(text); | 177 | let text = Arc::new(text); |
240 | let file = self.add_file(root, path.clone(), Arc::clone(&text), false); | 178 | let file = self.raw_add_file(root, path.clone(), Arc::clone(&text), false); |
241 | cur_files.push((file, path, text)); | 179 | cur_files.push((file, path, text)); |
242 | } | 180 | } |
243 | 181 | ||
@@ -245,15 +183,19 @@ impl Vfs { | |||
245 | self.pending_changes.push(change); | 183 | self.pending_changes.push(change); |
246 | } | 184 | } |
247 | TaskResult::SingleFile { root, path, text } => { | 185 | TaskResult::SingleFile { root, path, text } => { |
248 | match (self.find_file(root, &path), text) { | 186 | let existing_file = self.find_file(root, &path); |
187 | if existing_file.map(|file| self.files[file].is_overlayed) == Some(true) { | ||
188 | return; | ||
189 | } | ||
190 | match (existing_file, text) { | ||
249 | (Some(file), None) => { | 191 | (Some(file), None) => { |
250 | self.do_remove_file(root, path, file, false); | 192 | self.remove_file_event(root, path, file); |
251 | } | 193 | } |
252 | (None, Some(text)) => { | 194 | (None, Some(text)) => { |
253 | self.do_add_file(root, path, text, false); | 195 | self.add_file_event(root, path, text, false); |
254 | } | 196 | } |
255 | (Some(file), Some(text)) => { | 197 | (Some(file), Some(text)) => { |
256 | self.do_change_file(file, text, false); | 198 | self.change_file_event(file, text, false); |
257 | } | 199 | } |
258 | (None, None) => (), | 200 | (None, None) => (), |
259 | } | 201 | } |
@@ -261,7 +203,10 @@ impl Vfs { | |||
261 | } | 203 | } |
262 | } | 204 | } |
263 | 205 | ||
264 | fn do_add_file( | 206 | // *_event calls change the state of VFS and push a change onto pending |
207 | // changes array. | ||
208 | |||
209 | fn add_file_event( | ||
265 | &mut self, | 210 | &mut self, |
266 | root: VfsRoot, | 211 | root: VfsRoot, |
267 | path: RelativePathBuf, | 212 | path: RelativePathBuf, |
@@ -269,74 +214,25 @@ impl Vfs { | |||
269 | is_overlay: bool, | 214 | is_overlay: bool, |
270 | ) -> Option<VfsFile> { | 215 | ) -> Option<VfsFile> { |
271 | let text = Arc::new(text); | 216 | let text = Arc::new(text); |
272 | let file = self.add_file(root, path.clone(), text.clone(), is_overlay); | 217 | let file = self.raw_add_file(root, path.clone(), text.clone(), is_overlay); |
273 | self.pending_changes.push(VfsChange::AddFile { file, root, path, text }); | 218 | self.pending_changes.push(VfsChange::AddFile { file, root, path, text }); |
274 | Some(file) | 219 | Some(file) |
275 | } | 220 | } |
276 | 221 | ||
277 | fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) { | 222 | fn change_file_event(&mut self, file: VfsFile, text: String, is_overlay: bool) { |
278 | if !is_overlay && self.files[file].is_overlayed { | ||
279 | return; | ||
280 | } | ||
281 | let text = Arc::new(text); | 223 | let text = Arc::new(text); |
282 | self.change_file(file, text.clone(), is_overlay); | 224 | self.raw_change_file(file, text.clone(), is_overlay); |
283 | self.pending_changes.push(VfsChange::ChangeFile { file, text }); | 225 | self.pending_changes.push(VfsChange::ChangeFile { file, text }); |
284 | } | 226 | } |
285 | 227 | ||
286 | fn do_remove_file( | 228 | fn remove_file_event(&mut self, root: VfsRoot, path: RelativePathBuf, file: VfsFile) { |
287 | &mut self, | 229 | self.raw_remove_file(file); |
288 | root: VfsRoot, | ||
289 | path: RelativePathBuf, | ||
290 | file: VfsFile, | ||
291 | is_overlay: bool, | ||
292 | ) { | ||
293 | if !is_overlay && self.files[file].is_overlayed { | ||
294 | return; | ||
295 | } | ||
296 | self.remove_file(file); | ||
297 | self.pending_changes.push(VfsChange::RemoveFile { root, path, file }); | 230 | self.pending_changes.push(VfsChange::RemoveFile { root, path, file }); |
298 | } | 231 | } |
299 | 232 | ||
300 | pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> { | 233 | // raw_* calls change the state of VFS, but **do not** emit events. |
301 | if let Some((root, rel_path, file)) = self.find_root(path) { | ||
302 | if let Some(file) = file { | ||
303 | self.do_change_file(file, text, true); | ||
304 | Some(file) | ||
305 | } else { | ||
306 | self.do_add_file(root, rel_path, text, true) | ||
307 | } | ||
308 | } else { | ||
309 | None | ||
310 | } | ||
311 | } | ||
312 | |||
313 | pub fn change_file_overlay(&mut self, path: &Path, new_text: String) { | ||
314 | if let Some((_root, _path, file)) = self.find_root(path) { | ||
315 | let file = file.expect("can't change a file which wasn't added"); | ||
316 | self.do_change_file(file, new_text, true); | ||
317 | } | ||
318 | } | ||
319 | |||
320 | pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> { | ||
321 | if let Some((root, path, file)) = self.find_root(path) { | ||
322 | let file = file.expect("can't remove a file which wasn't added"); | ||
323 | let full_path = path.to_path(&self.roots[root].root); | ||
324 | if let Ok(text) = fs::read_to_string(&full_path) { | ||
325 | self.do_change_file(file, text, true); | ||
326 | } else { | ||
327 | self.do_remove_file(root, path, file, true); | ||
328 | } | ||
329 | Some(file) | ||
330 | } else { | ||
331 | None | ||
332 | } | ||
333 | } | ||
334 | |||
335 | pub fn commit_changes(&mut self) -> Vec<VfsChange> { | ||
336 | mem::replace(&mut self.pending_changes, Vec::new()) | ||
337 | } | ||
338 | 234 | ||
339 | fn add_file( | 235 | fn raw_add_file( |
340 | &mut self, | 236 | &mut self, |
341 | root: VfsRoot, | 237 | root: VfsRoot, |
342 | path: RelativePathBuf, | 238 | path: RelativePathBuf, |
@@ -349,13 +245,13 @@ impl Vfs { | |||
349 | file | 245 | file |
350 | } | 246 | } |
351 | 247 | ||
352 | fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) { | 248 | fn raw_change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) { |
353 | let mut file_data = &mut self.files[file]; | 249 | let mut file_data = &mut self.files[file]; |
354 | file_data.text = new_text; | 250 | file_data.text = new_text; |
355 | file_data.is_overlayed = is_overlayed; | 251 | file_data.is_overlayed = is_overlayed; |
356 | } | 252 | } |
357 | 253 | ||
358 | fn remove_file(&mut self, file: VfsFile) { | 254 | fn raw_remove_file(&mut self, file: VfsFile) { |
359 | // FIXME: use arena with removal | 255 | // FIXME: use arena with removal |
360 | self.files[file].text = Default::default(); | 256 | self.files[file].text = Default::default(); |
361 | self.files[file].path = Default::default(); | 257 | self.files[file].path = Default::default(); |