aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_vfs/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_vfs/src/lib.rs')
-rw-r--r--crates/ra_vfs/src/lib.rs248
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.
18mod roots;
18mod io; 19mod io;
19 20
20use std::{ 21use 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
27use crossbeam_channel::Receiver; 27use crossbeam_channel::Receiver;
28use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap}; 28use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap};
29use relative_path::{Component, RelativePath, RelativePathBuf}; 29use relative_path::{RelativePath, RelativePathBuf};
30use rustc_hash::{FxHashMap, FxHashSet}; 30use rustc_hash::{FxHashMap, FxHashSet};
31 31
32pub use crate::io::TaskResult as VfsTask; 32use crate::{
33use io::{TaskResult, Worker}; 33 io::{TaskResult, Worker},
34 34 roots::Roots,
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 35};
36pub struct VfsRoot(pub RawId);
37impl_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
44pub(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
51pub(crate) struct Roots {
52 roots: Arena<VfsRoot, Arc<RootConfig>>,
53}
54
55impl std::ops::Deref for Roots {
56 type Target = Arena<VfsRoot, Arc<RootConfig>>;
57 fn deref(&self) -> &Self::Target {
58 &self.roots
59 }
60}
61
62impl 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
106impl Roots { 37pub 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)]
131pub struct VfsFile(pub RawId); 43pub 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();