aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_vfs/Cargo.toml4
-rw-r--r--crates/ra_vfs/src/io.rs131
-rw-r--r--crates/ra_vfs/src/io/watcher.rs200
-rw-r--r--crates/ra_vfs/src/lib.rs359
-rw-r--r--crates/ra_vfs/tests/vfs.rs156
5 files changed, 647 insertions, 203 deletions
diff --git a/crates/ra_vfs/Cargo.toml b/crates/ra_vfs/Cargo.toml
index e637063c9..383381d2a 100644
--- a/crates/ra_vfs/Cargo.toml
+++ b/crates/ra_vfs/Cargo.toml
@@ -10,9 +10,13 @@ relative-path = "0.4.0"
10rustc-hash = "1.0" 10rustc-hash = "1.0"
11crossbeam-channel = "0.3.5" 11crossbeam-channel = "0.3.5"
12log = "0.4.6" 12log = "0.4.6"
13notify = "4.0.7"
14drop_bomb = "0.1.0"
15parking_lot = "0.7.0"
13 16
14thread_worker = { path = "../thread_worker" } 17thread_worker = { path = "../thread_worker" }
15ra_arena = { path = "../ra_arena" } 18ra_arena = { path = "../ra_arena" }
16 19
17[dev-dependencies] 20[dev-dependencies]
18tempfile = "3" 21tempfile = "3"
22flexi_logger = "0.10.0"
diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs
index 80328ad18..7ca1e9835 100644
--- a/crates/ra_vfs/src/io.rs
+++ b/crates/ra_vfs/src/io.rs
@@ -1,55 +1,109 @@
1use std::{ 1use std::{fs, sync::Arc, thread};
2 fmt,
3 fs,
4 path::{Path, PathBuf},
5};
6 2
7use walkdir::{DirEntry, WalkDir}; 3use crossbeam_channel::{Receiver, Sender};
8use thread_worker::{WorkerHandle};
9use relative_path::RelativePathBuf; 4use relative_path::RelativePathBuf;
5use thread_worker::WorkerHandle;
6use walkdir::WalkDir;
10 7
11use crate::{VfsRoot, has_rs_extension}; 8mod watcher;
9use watcher::Watcher;
12 10
13pub(crate) struct Task { 11use crate::{RootFilter, Roots, VfsRoot};
14 pub(crate) root: VfsRoot, 12
15 pub(crate) path: PathBuf, 13pub(crate) enum Task {
16 pub(crate) filter: Box<Fn(&DirEntry) -> bool + Send>, 14 AddRoot {
15 root: VfsRoot,
16 filter: Arc<RootFilter>,
17 },
17} 18}
18 19
19pub struct TaskResult { 20#[derive(Debug)]
20 pub(crate) root: VfsRoot, 21pub enum TaskResult {
21 pub(crate) files: Vec<(RelativePathBuf, String)>, 22 BulkLoadRoot {
23 root: VfsRoot,
24 files: Vec<(RelativePathBuf, String)>,
25 },
26 AddSingleFile {
27 root: VfsRoot,
28 path: RelativePathBuf,
29 text: String,
30 },
31 ChangeSingleFile {
32 root: VfsRoot,
33 path: RelativePathBuf,
34 text: String,
35 },
36 RemoveSingleFile {
37 root: VfsRoot,
38 path: RelativePathBuf,
39 },
22} 40}
23 41
24impl fmt::Debug for TaskResult { 42pub(crate) struct Worker {
25 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 43 worker: thread_worker::Worker<Task, TaskResult>,
26 f.write_str("TaskResult { ... }") 44 worker_handle: WorkerHandle,
27 }
28} 45}
29 46
30pub(crate) type Worker = thread_worker::Worker<Task, TaskResult>; 47impl Worker {
48 pub(crate) fn start(roots: Arc<Roots>) -> Worker {
49 let (worker, worker_handle) =
50 thread_worker::spawn("vfs", 128, move |input_receiver, output_sender| {
51 let mut watcher = match Watcher::start(roots, output_sender.clone()) {
52 Ok(w) => Some(w),
53 Err(e) => {
54 log::error!("could not start watcher: {}", e);
55 None
56 }
57 };
58 let res = input_receiver
59 .into_iter()
60 .filter_map(|t| handle_task(t, &mut watcher))
61 .try_for_each(|it| output_sender.send(it));
62 if let Some(watcher) = watcher {
63 let _ = watcher.shutdown();
64 }
65 res.unwrap()
66 });
67 Worker {
68 worker,
69 worker_handle,
70 }
71 }
72
73 pub(crate) fn sender(&self) -> &Sender<Task> {
74 &self.worker.inp
75 }
76
77 pub(crate) fn receiver(&self) -> &Receiver<TaskResult> {
78 &self.worker.out
79 }
31 80
32pub(crate) fn start() -> (Worker, WorkerHandle) { 81 pub(crate) fn shutdown(self) -> thread::Result<()> {
33 thread_worker::spawn("vfs", 128, |input_receiver, output_sender| { 82 let _ = self.worker.shutdown();
34 input_receiver 83 self.worker_handle.shutdown()
35 .into_iter() 84 }
36 .map(handle_task)
37 .try_for_each(|it| output_sender.send(it))
38 .unwrap()
39 })
40} 85}
41 86
42fn handle_task(task: Task) -> TaskResult { 87fn handle_task(task: Task, watcher: &mut Option<Watcher>) -> Option<TaskResult> {
43 let Task { root, path, filter } = task; 88 match task {
44 log::debug!("loading {} ...", path.as_path().display()); 89 Task::AddRoot { root, filter } => {
45 let files = load_root(path.as_path(), &*filter); 90 if let Some(watcher) = watcher {
46 log::debug!("... loaded {}", path.as_path().display()); 91 watcher.watch_root(&filter)
47 TaskResult { root, files } 92 }
93 log::debug!("loading {} ...", filter.root.as_path().display());
94 let files = load_root(filter.as_ref());
95 log::debug!("... loaded {}", filter.root.as_path().display());
96 Some(TaskResult::BulkLoadRoot { root, files })
97 }
98 }
48} 99}
49 100
50fn load_root(root: &Path, filter: &dyn Fn(&DirEntry) -> bool) -> Vec<(RelativePathBuf, String)> { 101fn load_root(filter: &RootFilter) -> Vec<(RelativePathBuf, String)> {
51 let mut res = Vec::new(); 102 let mut res = Vec::new();
52 for entry in WalkDir::new(root).into_iter().filter_entry(filter) { 103 for entry in WalkDir::new(&filter.root)
104 .into_iter()
105 .filter_entry(filter.entry_filter())
106 {
53 let entry = match entry { 107 let entry = match entry {
54 Ok(entry) => entry, 108 Ok(entry) => entry,
55 Err(e) => { 109 Err(e) => {
@@ -61,9 +115,6 @@ fn load_root(root: &Path, filter: &dyn Fn(&DirEntry) -> bool) -> Vec<(RelativePa
61 continue; 115 continue;
62 } 116 }
63 let path = entry.path(); 117 let path = entry.path();
64 if !has_rs_extension(path) {
65 continue;
66 }
67 let text = match fs::read_to_string(path) { 118 let text = match fs::read_to_string(path) {
68 Ok(text) => text, 119 Ok(text) => text,
69 Err(e) => { 120 Err(e) => {
@@ -71,7 +122,7 @@ fn load_root(root: &Path, filter: &dyn Fn(&DirEntry) -> bool) -> Vec<(RelativePa
71 continue; 122 continue;
72 } 123 }
73 }; 124 };
74 let path = RelativePathBuf::from_path(path.strip_prefix(root).unwrap()).unwrap(); 125 let path = RelativePathBuf::from_path(path.strip_prefix(&filter.root).unwrap()).unwrap();
75 res.push((path.to_owned(), text)) 126 res.push((path.to_owned(), text))
76 } 127 }
77 res 128 res
diff --git a/crates/ra_vfs/src/io/watcher.rs b/crates/ra_vfs/src/io/watcher.rs
new file mode 100644
index 000000000..ff6775f59
--- /dev/null
+++ b/crates/ra_vfs/src/io/watcher.rs
@@ -0,0 +1,200 @@
1use crate::{io, RootFilter, Roots, VfsRoot};
2use crossbeam_channel::Sender;
3use drop_bomb::DropBomb;
4use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher};
5use parking_lot::Mutex;
6use std::{
7 fs,
8 path::{Path, PathBuf},
9 sync::{mpsc, Arc},
10 thread,
11 time::Duration,
12};
13use walkdir::WalkDir;
14
15#[derive(Debug)]
16enum ChangeKind {
17 Create,
18 Write,
19 Remove,
20}
21
22const WATCHER_DELAY: Duration = Duration::from_millis(250);
23
24pub(crate) struct Watcher {
25 thread: thread::JoinHandle<()>,
26 bomb: DropBomb,
27 watcher: Arc<Mutex<Option<RecommendedWatcher>>>,
28}
29
30impl Watcher {
31 pub(crate) fn start(
32 roots: Arc<Roots>,
33 output_sender: Sender<io::TaskResult>,
34 ) -> Result<Watcher, Box<std::error::Error>> {
35 let (input_sender, input_receiver) = mpsc::channel();
36 let watcher = Arc::new(Mutex::new(Some(notify::watcher(
37 input_sender,
38 WATCHER_DELAY,
39 )?)));
40 let sender = output_sender.clone();
41 let watcher_clone = watcher.clone();
42 let thread = thread::spawn(move || {
43 let worker = WatcherWorker {
44 roots,
45 watcher: watcher_clone,
46 sender,
47 };
48 input_receiver
49 .into_iter()
50 // forward relevant events only
51 .try_for_each(|change| worker.handle_debounced_event(change))
52 .unwrap()
53 });
54 Ok(Watcher {
55 thread,
56 watcher,
57 bomb: DropBomb::new(format!("Watcher was not shutdown")),
58 })
59 }
60
61 pub fn watch_root(&mut self, filter: &RootFilter) {
62 for res in WalkDir::new(&filter.root)
63 .into_iter()
64 .filter_entry(filter.entry_filter())
65 {
66 match res {
67 Ok(entry) => {
68 if entry.file_type().is_dir() {
69 watch_one(self.watcher.as_ref(), entry.path());
70 }
71 }
72 Err(e) => log::warn!("watcher error: {}", e),
73 }
74 }
75 }
76
77 pub fn shutdown(mut self) -> thread::Result<()> {
78 self.bomb.defuse();
79 drop(self.watcher.lock().take());
80 let res = self.thread.join();
81 match &res {
82 Ok(()) => log::info!("... Watcher terminated with ok"),
83 Err(_) => log::error!("... Watcher terminated with err"),
84 }
85 res
86 }
87}
88
89struct WatcherWorker {
90 watcher: Arc<Mutex<Option<RecommendedWatcher>>>,
91 roots: Arc<Roots>,
92 sender: Sender<io::TaskResult>,
93}
94
95impl WatcherWorker {
96 fn handle_debounced_event(&self, ev: DebouncedEvent) -> Result<(), Box<std::error::Error>> {
97 match ev {
98 DebouncedEvent::NoticeWrite(_)
99 | DebouncedEvent::NoticeRemove(_)
100 | DebouncedEvent::Chmod(_) => {
101 // ignore
102 }
103 DebouncedEvent::Rescan => {
104 // TODO rescan all roots
105 }
106 DebouncedEvent::Create(path) => {
107 self.handle_change(path, ChangeKind::Create);
108 }
109 DebouncedEvent::Write(path) => {
110 self.handle_change(path, ChangeKind::Write);
111 }
112 DebouncedEvent::Remove(path) => {
113 self.handle_change(path, ChangeKind::Remove);
114 }
115 DebouncedEvent::Rename(src, dst) => {
116 self.handle_change(src, ChangeKind::Remove);
117 self.handle_change(dst, ChangeKind::Create);
118 }
119 DebouncedEvent::Error(err, path) => {
120 // TODO should we reload the file contents?
121 log::warn!("watcher error \"{}\", {:?}", err, path);
122 }
123 }
124 Ok(())
125 }
126
127 fn handle_change(&self, path: PathBuf, kind: ChangeKind) {
128 if let Err(e) = self.try_handle_change(path, kind) {
129 log::warn!("watcher error: {}", e)
130 }
131 }
132
133 fn try_handle_change(
134 &self,
135 path: PathBuf,
136 kind: ChangeKind,
137 ) -> Result<(), Box<std::error::Error>> {
138 let (root, rel_path) = match self.roots.find(&path) {
139 Some(x) => x,
140 None => return Ok(()),
141 };
142 match kind {
143 ChangeKind::Create => {
144 if path.is_dir() {
145 self.watch_recursive(&path, root);
146 } else {
147 let text = fs::read_to_string(&path)?;
148 self.sender.send(io::TaskResult::AddSingleFile {
149 root,
150 path: rel_path,
151 text,
152 })?
153 }
154 }
155 ChangeKind::Write => {
156 let text = fs::read_to_string(&path)?;
157 self.sender.send(io::TaskResult::ChangeSingleFile {
158 root,
159 path: rel_path,
160 text,
161 })?
162 }
163 ChangeKind::Remove => self.sender.send(io::TaskResult::RemoveSingleFile {
164 root,
165 path: rel_path,
166 })?,
167 }
168 Ok(())
169 }
170
171 fn watch_recursive(&self, dir: &Path, root: VfsRoot) {
172 let filter = &self.roots[root];
173 for res in WalkDir::new(dir)
174 .into_iter()
175 .filter_entry(filter.entry_filter())
176 {
177 match res {
178 Ok(entry) => {
179 if entry.file_type().is_dir() {
180 watch_one(self.watcher.as_ref(), entry.path());
181 } else {
182 // emit only for files otherwise we will cause watch_recursive to be called again with a dir that we are already watching
183 // emit as create because we haven't seen it yet
184 self.handle_change(entry.path().to_path_buf(), ChangeKind::Create);
185 }
186 }
187 Err(e) => log::warn!("watcher error: {}", e),
188 }
189 }
190 }
191}
192
193fn watch_one(watcher: &Mutex<Option<RecommendedWatcher>>, dir: &Path) {
194 if let Some(watcher) = watcher.lock().as_mut() {
195 match watcher.watch(dir, RecursiveMode::NonRecursive) {
196 Ok(()) => log::debug!("watching \"{}\"", dir.display()),
197 Err(e) => log::warn!("could not watch \"{}\": {}", dir.display(), e),
198 }
199 }
200}
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs
index cdea18d73..d1b0222e7 100644
--- a/crates/ra_vfs/src/lib.rs
+++ b/crates/ra_vfs/src/lib.rs
@@ -16,52 +16,77 @@
16mod io; 16mod io;
17 17
18use std::{ 18use std::{
19 fmt,
20 mem,
21 thread,
22 cmp::Reverse, 19 cmp::Reverse,
20 fmt, fs, mem,
21 ops::{Deref, DerefMut},
23 path::{Path, PathBuf}, 22 path::{Path, PathBuf},
24 ffi::OsStr,
25 sync::Arc, 23 sync::Arc,
26 fs, 24 thread,
27}; 25};
28 26
29use rustc_hash::{FxHashMap, FxHashSet};
30use relative_path::RelativePathBuf;
31use crossbeam_channel::Receiver; 27use crossbeam_channel::Receiver;
28use ra_arena::{impl_arena_id, Arena, RawId};
29use relative_path::{Component, RelativePath, RelativePathBuf};
30use rustc_hash::{FxHashMap, FxHashSet};
32use walkdir::DirEntry; 31use walkdir::DirEntry;
33use thread_worker::WorkerHandle;
34use ra_arena::{Arena, RawId, impl_arena_id};
35 32
36pub use crate::io::TaskResult as VfsTask; 33pub use crate::io::TaskResult as VfsTask;
34use io::{TaskResult, Worker};
37 35
38/// `RootFilter` is a predicate that checks if a file can belong to a root. If 36/// `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. 37/// several filters match a file (nested dirs), the most nested one wins.
40struct RootFilter { 38pub(crate) struct RootFilter {
41 root: PathBuf, 39 root: PathBuf,
42 file_filter: fn(&Path) -> bool, 40 filter: fn(&Path, &RelativePath) -> bool,
41 excluded_dirs: Vec<PathBuf>,
43} 42}
44 43
45impl RootFilter { 44impl RootFilter {
46 fn new(root: PathBuf) -> RootFilter { 45 fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootFilter {
47 RootFilter { 46 RootFilter {
48 root, 47 root,
49 file_filter: has_rs_extension, 48 filter: default_filter,
49 excluded_dirs,
50 } 50 }
51 } 51 }
52 /// Check if this root can contain `path`. NB: even if this returns 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. 53 /// true, the `path` might actually be conained in some nested root.
54 fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> { 54 pub(crate) fn can_contain(&self, path: &Path) -> Option<RelativePathBuf> {
55 if !(self.file_filter)(path) { 55 let rel_path = path.strip_prefix(&self.root).ok()?;
56 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
57 if !(self.filter)(path, rel_path.as_relative_path()) {
56 return None; 58 return None;
57 } 59 }
58 let path = path.strip_prefix(&self.root).ok()?; 60 Some(rel_path)
59 RelativePathBuf::from_path(path).ok() 61 }
62
63 pub(crate) fn entry_filter<'a>(&'a self) -> impl FnMut(&DirEntry) -> bool + 'a {
64 move |entry: &DirEntry| {
65 if entry.file_type().is_dir() && self.excluded_dirs.iter().any(|it| it == entry.path())
66 {
67 // do not walk nested roots
68 false
69 } else {
70 self.can_contain(entry.path()).is_some()
71 }
72 }
60 } 73 }
61} 74}
62 75
63fn has_rs_extension(p: &Path) -> bool { 76pub(crate) fn default_filter(path: &Path, rel_path: &RelativePath) -> bool {
64 p.extension() == Some(OsStr::new("rs")) 77 if path.is_dir() {
78 for (i, c) in rel_path.components().enumerate() {
79 if let Component::Normal(c) = c {
80 // TODO hardcoded for now
81 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
82 return false;
83 }
84 }
85 }
86 true
87 } else {
88 rel_path.extension() == Some("rs")
89 }
65} 90}
66 91
67#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 92#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -75,16 +100,58 @@ impl_arena_id!(VfsFile);
75struct VfsFileData { 100struct VfsFileData {
76 root: VfsRoot, 101 root: VfsRoot,
77 path: RelativePathBuf, 102 path: RelativePathBuf,
103 is_overlayed: bool,
78 text: Arc<String>, 104 text: Arc<String>,
79} 105}
80 106
107pub(crate) struct Roots {
108 roots: Arena<VfsRoot, Arc<RootFilter>>,
109}
110
111impl Roots {
112 pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
113 let mut roots = Arena::default();
114 // A hack to make nesting work.
115 paths.sort_by_key(|it| Reverse(it.as_os_str().len()));
116 for (i, path) in paths.iter().enumerate() {
117 let nested_roots = paths[..i]
118 .iter()
119 .filter(|it| it.starts_with(path))
120 .map(|it| it.clone())
121 .collect::<Vec<_>>();
122
123 let root_filter = Arc::new(RootFilter::new(path.clone(), nested_roots));
124
125 roots.alloc(root_filter.clone());
126 }
127 Roots { roots }
128 }
129 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
130 self.roots
131 .iter()
132 .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))
133 }
134}
135
136impl Deref for Roots {
137 type Target = Arena<VfsRoot, Arc<RootFilter>>;
138 fn deref(&self) -> &Self::Target {
139 &self.roots
140 }
141}
142
143impl DerefMut for Roots {
144 fn deref_mut(&mut self) -> &mut Self::Target {
145 &mut self.roots
146 }
147}
148
81pub struct Vfs { 149pub struct Vfs {
82 roots: Arena<VfsRoot, RootFilter>, 150 roots: Arc<Roots>,
83 files: Arena<VfsFile, VfsFileData>, 151 files: Arena<VfsFile, VfsFileData>,
84 root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>, 152 root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
85 pending_changes: Vec<VfsChange>, 153 pending_changes: Vec<VfsChange>,
86 worker: io::Worker, 154 worker: Worker,
87 worker_handle: WorkerHandle,
88} 155}
89 156
90impl fmt::Debug for Vfs { 157impl fmt::Debug for Vfs {
@@ -94,44 +161,30 @@ impl fmt::Debug for Vfs {
94} 161}
95 162
96impl Vfs { 163impl Vfs {
97 pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { 164 pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
98 let (worker, worker_handle) = io::start(); 165 let roots = Arc::new(Roots::new(roots));
99 166 let worker = io::Worker::start(roots.clone());
100 let mut res = Vfs { 167 let mut root2files = FxHashMap::default();
101 roots: Arena::default(), 168
169 for (root, filter) in roots.iter() {
170 root2files.insert(root, Default::default());
171 worker
172 .sender()
173 .send(io::Task::AddRoot {
174 root,
175 filter: filter.clone(),
176 })
177 .unwrap();
178 }
179 let res = Vfs {
180 roots,
102 files: Arena::default(), 181 files: Arena::default(),
103 root2files: FxHashMap::default(), 182 root2files,
104 worker, 183 worker,
105 worker_handle,
106 pending_changes: Vec::new(), 184 pending_changes: Vec::new(),
107 }; 185 };
108 186 let vfs_roots = res.roots.iter().map(|(id, _)| id).collect();
109 // A hack to make nesting work. 187 (res, vfs_roots)
110 roots.sort_by_key(|it| Reverse(it.as_os_str().len()));
111 for (i, path) in roots.iter().enumerate() {
112 let root = res.roots.alloc(RootFilter::new(path.clone()));
113 res.root2files.insert(root, Default::default());
114 let nested = roots[..i]
115 .iter()
116 .filter(|it| it.starts_with(path))
117 .map(|it| it.clone())
118 .collect::<Vec<_>>();
119 let filter = move |entry: &DirEntry| {
120 if entry.file_type().is_file() {
121 has_rs_extension(entry.path())
122 } else {
123 nested.iter().all(|it| it != entry.path())
124 }
125 };
126 let task = io::Task {
127 root,
128 path: path.clone(),
129 filter: Box::new(filter),
130 };
131 res.worker.inp.send(task).unwrap();
132 }
133 let roots = res.roots.iter().map(|(id, _)| id).collect();
134 (res, roots)
135 } 188 }
136 189
137 pub fn root2path(&self, root: VfsRoot) -> PathBuf { 190 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
@@ -165,7 +218,7 @@ impl Vfs {
165 } else { 218 } else {
166 let text = fs::read_to_string(path).unwrap_or_default(); 219 let text = fs::read_to_string(path).unwrap_or_default();
167 let text = Arc::new(text); 220 let text = Arc::new(text);
168 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text)); 221 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
169 let change = VfsChange::AddFile { 222 let change = VfsChange::AddFile {
170 file, 223 file,
171 text, 224 text,
@@ -180,85 +233,130 @@ impl Vfs {
180 } 233 }
181 234
182 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> { 235 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
183 &self.worker.out 236 self.worker.receiver()
184 } 237 }
185 238
186 pub fn handle_task(&mut self, task: io::TaskResult) { 239 pub fn handle_task(&mut self, task: io::TaskResult) {
187 let mut files = Vec::new(); 240 match task {
188 // While we were scanning the root in the backgound, a file might have 241 TaskResult::BulkLoadRoot { root, files } => {
189 // been open in the editor, so we need to account for that. 242 let mut cur_files = Vec::new();
190 let exising = self.root2files[&task.root] 243 // While we were scanning the root in the backgound, a file might have
191 .iter() 244 // been open in the editor, so we need to account for that.
192 .map(|&file| (self.files[file].path.clone(), file)) 245 let exising = self.root2files[&root]
193 .collect::<FxHashMap<_, _>>(); 246 .iter()
194 for (path, text) in task.files { 247 .map(|&file| (self.files[file].path.clone(), file))
195 if let Some(&file) = exising.get(&path) { 248 .collect::<FxHashMap<_, _>>();
196 let text = Arc::clone(&self.files[file].text); 249 for (path, text) in files {
197 files.push((file, path, text)); 250 if let Some(&file) = exising.get(&path) {
198 continue; 251 let text = Arc::clone(&self.files[file].text);
252 cur_files.push((file, path, text));
253 continue;
254 }
255 let text = Arc::new(text);
256 let file = self.add_file(root, path.clone(), Arc::clone(&text), false);
257 cur_files.push((file, path, text));
258 }
259
260 let change = VfsChange::AddRoot {
261 root,
262 files: cur_files,
263 };
264 self.pending_changes.push(change);
265 }
266 TaskResult::AddSingleFile { root, path, text } => {
267 self.do_add_file(root, path, text, false);
268 }
269 TaskResult::ChangeSingleFile { root, path, text } => {
270 if let Some(file) = self.find_file(root, &path) {
271 self.do_change_file(file, text, false);
272 } else {
273 self.do_add_file(root, path, text, false);
274 }
275 }
276 TaskResult::RemoveSingleFile { root, path } => {
277 if let Some(file) = self.find_file(root, &path) {
278 self.do_remove_file(root, path, file, false);
279 }
199 } 280 }
200 let text = Arc::new(text);
201 let file = self.add_file(task.root, path.clone(), Arc::clone(&text));
202 files.push((file, path, text));
203 } 281 }
282 }
204 283
205 let change = VfsChange::AddRoot { 284 fn do_add_file(
206 root: task.root, 285 &mut self,
207 files, 286 root: VfsRoot,
208 }; 287 path: RelativePathBuf,
209 self.pending_changes.push(change); 288 text: String,
289 is_overlay: bool,
290 ) -> Option<VfsFile> {
291 let text = Arc::new(text);
292 let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
293 self.pending_changes.push(VfsChange::AddFile {
294 file,
295 root,
296 path,
297 text,
298 });
299 Some(file)
300 }
301
302 fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
303 if !is_overlay && self.files[file].is_overlayed {
304 return;
305 }
306 let text = Arc::new(text);
307 self.change_file(file, text.clone(), is_overlay);
308 self.pending_changes
309 .push(VfsChange::ChangeFile { file, text });
310 }
311
312 fn do_remove_file(
313 &mut self,
314 root: VfsRoot,
315 path: RelativePathBuf,
316 file: VfsFile,
317 is_overlay: bool,
318 ) {
319 if !is_overlay && self.files[file].is_overlayed {
320 return;
321 }
322 self.remove_file(file);
323 self.pending_changes
324 .push(VfsChange::RemoveFile { root, path, file });
210 } 325 }
211 326
212 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> { 327 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
213 let mut res = None; 328 if let Some((root, rel_path, file)) = self.find_root(path) {
214 if let Some((root, path, file)) = self.find_root(path) { 329 if let Some(file) = file {
215 let text = Arc::new(text); 330 self.do_change_file(file, text, true);
216 let change = if let Some(file) = file { 331 Some(file)
217 res = Some(file);
218 self.change_file(file, Arc::clone(&text));
219 VfsChange::ChangeFile { file, text }
220 } else { 332 } else {
221 let file = self.add_file(root, path.clone(), Arc::clone(&text)); 333 self.do_add_file(root, rel_path, text, true)
222 res = Some(file); 334 }
223 VfsChange::AddFile { 335 } else {
224 file, 336 None
225 text,
226 root,
227 path,
228 }
229 };
230 self.pending_changes.push(change);
231 } 337 }
232 res
233 } 338 }
234 339
235 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) { 340 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
236 if let Some((_root, _path, file)) = self.find_root(path) { 341 if let Some((_root, _path, file)) = self.find_root(path) {
237 let file = file.expect("can't change a file which wasn't added"); 342 let file = file.expect("can't change a file which wasn't added");
238 let text = Arc::new(new_text); 343 self.do_change_file(file, new_text, true);
239 self.change_file(file, Arc::clone(&text));
240 let change = VfsChange::ChangeFile { file, text };
241 self.pending_changes.push(change);
242 } 344 }
243 } 345 }
244 346
245 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> { 347 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
246 let mut res = None;
247 if let Some((root, path, file)) = self.find_root(path) { 348 if let Some((root, path, file)) = self.find_root(path) {
248 let file = file.expect("can't remove a file which wasn't added"); 349 let file = file.expect("can't remove a file which wasn't added");
249 res = Some(file);
250 let full_path = path.to_path(&self.roots[root].root); 350 let full_path = path.to_path(&self.roots[root].root);
251 let change = if let Ok(text) = fs::read_to_string(&full_path) { 351 if let Ok(text) = fs::read_to_string(&full_path) {
252 let text = Arc::new(text); 352 self.do_change_file(file, text, true);
253 self.change_file(file, Arc::clone(&text));
254 VfsChange::ChangeFile { file, text }
255 } else { 353 } else {
256 self.remove_file(file); 354 self.do_remove_file(root, path, file, true);
257 VfsChange::RemoveFile { root, file, path } 355 }
258 }; 356 Some(file)
259 self.pending_changes.push(change); 357 } else {
358 None
260 } 359 }
261 res
262 } 360 }
263 361
264 pub fn commit_changes(&mut self) -> Vec<VfsChange> { 362 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
@@ -267,19 +365,31 @@ impl Vfs {
267 365
268 /// Sutdown the VFS and terminate the background watching thread. 366 /// Sutdown the VFS and terminate the background watching thread.
269 pub fn shutdown(self) -> thread::Result<()> { 367 pub fn shutdown(self) -> thread::Result<()> {
270 let _ = self.worker.shutdown(); 368 self.worker.shutdown()
271 self.worker_handle.shutdown()
272 } 369 }
273 370
274 fn add_file(&mut self, root: VfsRoot, path: RelativePathBuf, text: Arc<String>) -> VfsFile { 371 fn add_file(
275 let data = VfsFileData { root, path, text }; 372 &mut self,
373 root: VfsRoot,
374 path: RelativePathBuf,
375 text: Arc<String>,
376 is_overlayed: bool,
377 ) -> VfsFile {
378 let data = VfsFileData {
379 root,
380 path,
381 text,
382 is_overlayed,
383 };
276 let file = self.files.alloc(data); 384 let file = self.files.alloc(data);
277 self.root2files.get_mut(&root).unwrap().insert(file); 385 self.root2files.get_mut(&root).unwrap().insert(file);
278 file 386 file
279 } 387 }
280 388
281 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>) { 389 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
282 self.files[file].text = new_text; 390 let mut file_data = &mut self.files[file];
391 file_data.text = new_text;
392 file_data.is_overlayed = is_overlayed;
283 } 393 }
284 394
285 fn remove_file(&mut self, file: VfsFile) { 395 fn remove_file(&mut self, file: VfsFile) {
@@ -292,15 +402,16 @@ impl Vfs {
292 } 402 }
293 403
294 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> { 404 fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> {
295 let (root, path) = self 405 let (root, path) = self.roots.find(&path)?;
296 .roots 406 let file = self.find_file(root, &path);
297 .iter() 407 Some((root, path, file))
298 .find_map(|(root, data)| data.can_contain(path).map(|it| (root, it)))?; 408 }
299 let file = self.root2files[&root] 409
410 fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
411 self.root2files[&root]
300 .iter() 412 .iter()
301 .map(|&it| it) 413 .map(|&it| it)
302 .find(|&file| self.files[file].path == path); 414 .find(|&file| self.files[file].path == path)
303 Some((root, path, file))
304 } 415 }
305} 416}
306 417
diff --git a/crates/ra_vfs/tests/vfs.rs b/crates/ra_vfs/tests/vfs.rs
index f56fc4603..357e1c775 100644
--- a/crates/ra_vfs/tests/vfs.rs
+++ b/crates/ra_vfs/tests/vfs.rs
@@ -1,24 +1,47 @@
1use std::{ 1use std::{collections::HashSet, fs, time::Duration};
2 fs,
3 collections::HashSet,
4};
5 2
3// use flexi_logger::Logger;
4use crossbeam_channel::RecvTimeoutError;
5use ra_vfs::{Vfs, VfsChange};
6use tempfile::tempdir; 6use tempfile::tempdir;
7 7
8use ra_vfs::{Vfs, VfsChange}; 8fn process_tasks(vfs: &mut Vfs, num_tasks: u32) {
9 for _ in 0..num_tasks {
10 let task = vfs
11 .task_receiver()
12 .recv_timeout(Duration::from_secs(3))
13 .unwrap();
14 log::debug!("{:?}", task);
15 vfs.handle_task(task);
16 }
17}
18
19macro_rules! assert_match {
20 ($x:expr, $pat:pat) => {
21 assert_match!($x, $pat, ())
22 };
23 ($x:expr, $pat:pat, $assert:expr) => {
24 match $x {
25 $pat => $assert,
26 x => assert!(false, "Expected {}, got {:?}", stringify!($pat), x),
27 };
28 };
29}
9 30
10#[test] 31#[test]
11fn test_vfs_works() -> std::io::Result<()> { 32fn test_vfs_works() -> std::io::Result<()> {
33 // Logger::with_str("vfs=debug,ra_vfs=debug").start().unwrap();
34
12 let files = [ 35 let files = [
13 ("a/foo.rs", "hello"), 36 ("a/foo.rs", "hello"),
14 ("a/bar.rs", "world"), 37 ("a/bar.rs", "world"),
15 ("a/b/baz.rs", "nested hello"), 38 ("a/b/baz.rs", "nested hello"),
16 ]; 39 ];
17 40
18 let dir = tempdir()?; 41 let dir = tempdir().unwrap();
19 for (path, text) in files.iter() { 42 for (path, text) in files.iter() {
20 let file_path = dir.path().join(path); 43 let file_path = dir.path().join(path);
21 fs::create_dir_all(file_path.parent().unwrap())?; 44 fs::create_dir_all(file_path.parent().unwrap()).unwrap();
22 fs::write(file_path, text)? 45 fs::write(file_path, text)?
23 } 46 }
24 47
@@ -26,10 +49,7 @@ fn test_vfs_works() -> std::io::Result<()> {
26 let b_root = dir.path().join("a/b"); 49 let b_root = dir.path().join("a/b");
27 50
28 let (mut vfs, _) = Vfs::new(vec![a_root, b_root]); 51 let (mut vfs, _) = Vfs::new(vec![a_root, b_root]);
29 for _ in 0..2 { 52 process_tasks(&mut vfs, 2);
30 let task = vfs.task_receiver().recv().unwrap();
31 vfs.handle_task(task);
32 }
33 { 53 {
34 let files = vfs 54 let files = vfs
35 .commit_changes() 55 .commit_changes()
@@ -58,43 +78,101 @@ fn test_vfs_works() -> std::io::Result<()> {
58 assert_eq!(files, expected_files); 78 assert_eq!(files, expected_files);
59 } 79 }
60 80
61 vfs.add_file_overlay(&dir.path().join("a/b/baz.rs"), "quux".to_string()); 81 fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap();
62 let change = vfs.commit_changes().pop().unwrap(); 82 process_tasks(&mut vfs, 1);
63 match change { 83 assert_match!(
64 VfsChange::ChangeFile { text, .. } => assert_eq!(&*text, "quux"), 84 vfs.commit_changes().as_slice(),
65 _ => panic!("unexpected change"), 85 [VfsChange::ChangeFile { text, .. }],
66 } 86 assert_eq!(text.as_str(), "quux")
87 );
67 88
68 vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), "m".to_string()); 89 vfs.add_file_overlay(&dir.path().join("a/b/baz.rs"), "m".to_string());
69 let change = vfs.commit_changes().pop().unwrap(); 90 assert_match!(
70 match change { 91 vfs.commit_changes().as_slice(),
71 VfsChange::ChangeFile { text, .. } => assert_eq!(&*text, "m"), 92 [VfsChange::ChangeFile { text, .. }],
72 _ => panic!("unexpected change"), 93 assert_eq!(text.as_str(), "m")
73 } 94 );
74 95
96 // changing file on disk while overlayed doesn't generate a VfsChange
97 fs::write(&dir.path().join("a/b/baz.rs"), "corge").unwrap();
98 process_tasks(&mut vfs, 1);
99 assert_match!(vfs.commit_changes().as_slice(), []);
100
101 // removing overlay restores data on disk
75 vfs.remove_file_overlay(&dir.path().join("a/b/baz.rs")); 102 vfs.remove_file_overlay(&dir.path().join("a/b/baz.rs"));
76 let change = vfs.commit_changes().pop().unwrap(); 103 assert_match!(
77 match change { 104 vfs.commit_changes().as_slice(),
78 VfsChange::ChangeFile { text, .. } => assert_eq!(&*text, "nested hello"), 105 [VfsChange::ChangeFile { text, .. }],
79 _ => panic!("unexpected change"), 106 assert_eq!(text.as_str(), "corge")
80 } 107 );
81 108
82 vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), "spam".to_string()); 109 vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), "spam".to_string());
83 let change = vfs.commit_changes().pop().unwrap(); 110 assert_match!(
84 match change { 111 vfs.commit_changes().as_slice(),
85 VfsChange::AddFile { text, path, .. } => { 112 [VfsChange::AddFile { text, path, .. }],
86 assert_eq!(&*text, "spam"); 113 {
114 assert_eq!(text.as_str(), "spam");
87 assert_eq!(path, "spam.rs"); 115 assert_eq!(path, "spam.rs");
88 } 116 }
89 _ => panic!("unexpected change"), 117 );
90 }
91 118
92 vfs.remove_file_overlay(&dir.path().join("a/b/spam.rs")); 119 vfs.remove_file_overlay(&dir.path().join("a/b/spam.rs"));
93 let change = vfs.commit_changes().pop().unwrap(); 120 assert_match!(
94 match change { 121 vfs.commit_changes().as_slice(),
95 VfsChange::RemoveFile { .. } => (), 122 [VfsChange::RemoveFile { path, .. }],
96 _ => panic!("unexpected change"), 123 assert_eq!(path, "spam.rs")
97 } 124 );
125
126 fs::create_dir_all(dir.path().join("a/sub1/sub2")).unwrap();
127 fs::write(dir.path().join("a/sub1/sub2/new.rs"), "new hello").unwrap();
128 process_tasks(&mut vfs, 1);
129 assert_match!(
130 vfs.commit_changes().as_slice(),
131 [VfsChange::AddFile { text, path, .. }],
132 {
133 assert_eq!(text.as_str(), "new hello");
134 assert_eq!(path, "sub1/sub2/new.rs");
135 }
136 );
137
138 fs::rename(
139 &dir.path().join("a/sub1/sub2/new.rs"),
140 &dir.path().join("a/sub1/sub2/new1.rs"),
141 )
142 .unwrap();
143 process_tasks(&mut vfs, 2);
144 assert_match!(
145 vfs.commit_changes().as_slice(),
146 [VfsChange::RemoveFile {
147 path: removed_path, ..
148 }, VfsChange::AddFile {
149 text,
150 path: added_path,
151 ..
152 }],
153 {
154 assert_eq!(removed_path, "sub1/sub2/new.rs");
155 assert_eq!(added_path, "sub1/sub2/new1.rs");
156 assert_eq!(text.as_str(), "new hello");
157 }
158 );
159
160 fs::remove_file(&dir.path().join("a/sub1/sub2/new1.rs")).unwrap();
161 process_tasks(&mut vfs, 1);
162 assert_match!(
163 vfs.commit_changes().as_slice(),
164 [VfsChange::RemoveFile { path, .. }],
165 assert_eq!(path, "sub1/sub2/new1.rs")
166 );
167
168 // should be ignored
169 fs::create_dir_all(dir.path().join("a/target")).unwrap();
170 fs::write(&dir.path().join("a/target/new.rs"), "ignore me").unwrap();
171
172 assert_match!(
173 vfs.task_receiver().recv_timeout(Duration::from_millis(300)), // slightly more than watcher debounce delay
174 Err(RecvTimeoutError::Timeout)
175 );
98 176
99 vfs.shutdown().unwrap(); 177 vfs.shutdown().unwrap();
100 Ok(()) 178 Ok(())