diff options
-rw-r--r-- | crates/ra_vfs/src/io.rs | 200 | ||||
-rw-r--r-- | crates/ra_vfs/src/io/watcher.rs | 215 | ||||
-rw-r--r-- | crates/ra_vfs/src/lib.rs | 168 | ||||
-rw-r--r-- | crates/ra_vfs/tests/vfs.rs | 21 |
4 files changed, 274 insertions, 330 deletions
diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs index 83a021c2f..7ca1e9835 100644 --- a/crates/ra_vfs/src/io.rs +++ b/crates/ra_vfs/src/io.rs | |||
@@ -1,95 +1,72 @@ | |||
1 | use std::{ | 1 | use std::{fs, sync::Arc, thread}; |
2 | fmt, fs, | ||
3 | path::{Path, PathBuf}, | ||
4 | sync::Arc, | ||
5 | thread, | ||
6 | }; | ||
7 | 2 | ||
8 | use crossbeam_channel::{Receiver, Sender}; | 3 | use crossbeam_channel::{Receiver, Sender}; |
9 | use parking_lot::Mutex; | ||
10 | use relative_path::RelativePathBuf; | 4 | use relative_path::RelativePathBuf; |
11 | use thread_worker::WorkerHandle; | 5 | use thread_worker::WorkerHandle; |
12 | use walkdir::WalkDir; | 6 | use walkdir::WalkDir; |
13 | 7 | ||
14 | mod watcher; | 8 | mod watcher; |
15 | use watcher::Watcher; | 9 | use watcher::Watcher; |
16 | pub use watcher::WatcherChange; | ||
17 | 10 | ||
18 | use crate::{RootFilter, VfsRoot}; | 11 | use crate::{RootFilter, Roots, VfsRoot}; |
19 | 12 | ||
20 | pub(crate) enum Task { | 13 | pub(crate) enum Task { |
21 | AddRoot { | 14 | AddRoot { |
22 | root: VfsRoot, | 15 | root: VfsRoot, |
23 | path: PathBuf, | 16 | filter: Arc<RootFilter>, |
24 | root_filter: Arc<RootFilter>, | ||
25 | nested_roots: Vec<PathBuf>, | ||
26 | }, | ||
27 | /// this variant should only be created by the watcher | ||
28 | HandleChange(WatcherChange), | ||
29 | LoadChange(WatcherChange), | ||
30 | Watch { | ||
31 | dir: PathBuf, | ||
32 | root_filter: Arc<RootFilter>, | ||
33 | }, | 17 | }, |
34 | } | 18 | } |
35 | 19 | ||
36 | #[derive(Debug)] | 20 | #[derive(Debug)] |
37 | pub struct AddRootResult { | ||
38 | pub(crate) root: VfsRoot, | ||
39 | pub(crate) files: Vec<(RelativePathBuf, String)>, | ||
40 | } | ||
41 | |||
42 | #[derive(Debug)] | ||
43 | pub enum WatcherChangeData { | ||
44 | Create { path: PathBuf, text: String }, | ||
45 | Write { path: PathBuf, text: String }, | ||
46 | Remove { path: PathBuf }, | ||
47 | } | ||
48 | |||
49 | pub enum TaskResult { | 21 | pub enum TaskResult { |
50 | AddRoot(AddRootResult), | 22 | BulkLoadRoot { |
51 | HandleChange(WatcherChange), | 23 | root: VfsRoot, |
52 | LoadChange(WatcherChangeData), | 24 | files: Vec<(RelativePathBuf, String)>, |
53 | } | 25 | }, |
54 | 26 | AddSingleFile { | |
55 | impl fmt::Debug for TaskResult { | 27 | root: VfsRoot, |
56 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | 28 | path: RelativePathBuf, |
57 | match self { | 29 | text: String, |
58 | TaskResult::AddRoot(..) => f.write_str("TaskResult::AddRoot(..)"), | 30 | }, |
59 | TaskResult::HandleChange(c) => write!(f, "TaskResult::HandleChange({:?})", c), | 31 | ChangeSingleFile { |
60 | TaskResult::LoadChange(c) => write!(f, "TaskResult::LoadChange({:?})", c), | 32 | root: VfsRoot, |
61 | } | 33 | path: RelativePathBuf, |
62 | } | 34 | text: String, |
35 | }, | ||
36 | RemoveSingleFile { | ||
37 | root: VfsRoot, | ||
38 | path: RelativePathBuf, | ||
39 | }, | ||
63 | } | 40 | } |
64 | 41 | ||
65 | pub(crate) struct Worker { | 42 | pub(crate) struct Worker { |
66 | worker: thread_worker::Worker<Task, TaskResult>, | 43 | worker: thread_worker::Worker<Task, TaskResult>, |
67 | worker_handle: WorkerHandle, | 44 | worker_handle: WorkerHandle, |
68 | watcher: Arc<Mutex<Option<Watcher>>>, | ||
69 | } | 45 | } |
70 | 46 | ||
71 | impl Worker { | 47 | impl Worker { |
72 | pub(crate) fn start() -> Worker { | 48 | pub(crate) fn start(roots: Arc<Roots>) -> Worker { |
73 | let watcher = Arc::new(Mutex::new(None)); | ||
74 | let watcher_clone = watcher.clone(); | ||
75 | let (worker, worker_handle) = | 49 | let (worker, worker_handle) = |
76 | thread_worker::spawn("vfs", 128, move |input_receiver, output_sender| { | 50 | thread_worker::spawn("vfs", 128, move |input_receiver, output_sender| { |
77 | input_receiver | 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 | ||
78 | .into_iter() | 59 | .into_iter() |
79 | .filter_map(|t| handle_task(t, &watcher_clone)) | 60 | .filter_map(|t| handle_task(t, &mut watcher)) |
80 | .try_for_each(|it| output_sender.send(it)) | 61 | .try_for_each(|it| output_sender.send(it)); |
81 | .unwrap() | 62 | if let Some(watcher) = watcher { |
63 | let _ = watcher.shutdown(); | ||
64 | } | ||
65 | res.unwrap() | ||
82 | }); | 66 | }); |
83 | match Watcher::start(worker.inp.clone()) { | ||
84 | Ok(w) => { | ||
85 | watcher.lock().replace(w); | ||
86 | } | ||
87 | Err(e) => log::error!("could not start watcher: {}", e), | ||
88 | }; | ||
89 | Worker { | 67 | Worker { |
90 | worker, | 68 | worker, |
91 | worker_handle, | 69 | worker_handle, |
92 | watcher, | ||
93 | } | 70 | } |
94 | } | 71 | } |
95 | 72 | ||
@@ -102,72 +79,31 @@ impl Worker { | |||
102 | } | 79 | } |
103 | 80 | ||
104 | pub(crate) fn shutdown(self) -> thread::Result<()> { | 81 | pub(crate) fn shutdown(self) -> thread::Result<()> { |
105 | if let Some(watcher) = self.watcher.lock().take() { | ||
106 | let _ = watcher.shutdown(); | ||
107 | } | ||
108 | let _ = self.worker.shutdown(); | 82 | let _ = self.worker.shutdown(); |
109 | self.worker_handle.shutdown() | 83 | self.worker_handle.shutdown() |
110 | } | 84 | } |
111 | } | 85 | } |
112 | 86 | ||
113 | fn watch( | 87 | fn handle_task(task: Task, watcher: &mut Option<Watcher>) -> Option<TaskResult> { |
114 | watcher: &Arc<Mutex<Option<Watcher>>>, | ||
115 | dir: &Path, | ||
116 | filter_entry: &RootFilter, | ||
117 | emit_for_existing: bool, | ||
118 | ) { | ||
119 | if let Some(watcher) = watcher.lock().as_mut() { | ||
120 | watcher.watch_recursive(dir, filter_entry, emit_for_existing) | ||
121 | } | ||
122 | } | ||
123 | |||
124 | fn handle_task(task: Task, watcher: &Arc<Mutex<Option<Watcher>>>) -> Option<TaskResult> { | ||
125 | match task { | 88 | match task { |
126 | Task::AddRoot { | 89 | Task::AddRoot { root, filter } => { |
127 | root, | 90 | if let Some(watcher) = watcher { |
128 | path, | 91 | watcher.watch_root(&filter) |
129 | root_filter, | 92 | } |
130 | nested_roots, | 93 | log::debug!("loading {} ...", filter.root.as_path().display()); |
131 | } => { | 94 | let files = load_root(filter.as_ref()); |
132 | watch(watcher, &path, root_filter.as_ref(), false); | 95 | log::debug!("... loaded {}", filter.root.as_path().display()); |
133 | log::debug!("loading {} ...", path.as_path().display()); | 96 | Some(TaskResult::BulkLoadRoot { root, files }) |
134 | let files = load_root( | ||
135 | path.as_path(), | ||
136 | root_filter.as_ref(), | ||
137 | nested_roots.as_slice(), | ||
138 | ); | ||
139 | log::debug!("... loaded {}", path.as_path().display()); | ||
140 | Some(TaskResult::AddRoot(AddRootResult { root, files })) | ||
141 | } | ||
142 | Task::HandleChange(change) => { | ||
143 | // forward as is because Vfs has to decide if we should load it | ||
144 | Some(TaskResult::HandleChange(change)) | ||
145 | } | ||
146 | Task::LoadChange(change) => { | ||
147 | log::debug!("loading {:?} ...", change); | ||
148 | load_change(change).map(TaskResult::LoadChange) | ||
149 | } | ||
150 | Task::Watch { dir, root_filter } => { | ||
151 | watch(watcher, &dir, root_filter.as_ref(), true); | ||
152 | None | ||
153 | } | 97 | } |
154 | } | 98 | } |
155 | } | 99 | } |
156 | 100 | ||
157 | fn load_root( | 101 | fn load_root(filter: &RootFilter) -> Vec<(RelativePathBuf, String)> { |
158 | root: &Path, | ||
159 | root_filter: &RootFilter, | ||
160 | nested_roots: &[PathBuf], | ||
161 | ) -> Vec<(RelativePathBuf, String)> { | ||
162 | let mut res = Vec::new(); | 102 | let mut res = Vec::new(); |
163 | for entry in WalkDir::new(root).into_iter().filter_entry(|entry| { | 103 | for entry in WalkDir::new(&filter.root) |
164 | if entry.file_type().is_dir() && nested_roots.iter().any(|it| it == entry.path()) { | 104 | .into_iter() |
165 | // do not load files of a nested root | 105 | .filter_entry(filter.entry_filter()) |
166 | false | 106 | { |
167 | } else { | ||
168 | root_filter.can_contain(entry.path()).is_some() | ||
169 | } | ||
170 | }) { | ||
171 | let entry = match entry { | 107 | let entry = match entry { |
172 | Ok(entry) => entry, | 108 | Ok(entry) => entry, |
173 | Err(e) => { | 109 | Err(e) => { |
@@ -186,42 +122,8 @@ fn load_root( | |||
186 | continue; | 122 | continue; |
187 | } | 123 | } |
188 | }; | 124 | }; |
189 | let path = RelativePathBuf::from_path(path.strip_prefix(root).unwrap()).unwrap(); | 125 | let path = RelativePathBuf::from_path(path.strip_prefix(&filter.root).unwrap()).unwrap(); |
190 | res.push((path.to_owned(), text)) | 126 | res.push((path.to_owned(), text)) |
191 | } | 127 | } |
192 | res | 128 | res |
193 | } | 129 | } |
194 | |||
195 | fn load_change(change: WatcherChange) -> Option<WatcherChangeData> { | ||
196 | let data = match change { | ||
197 | WatcherChange::Create(path) => { | ||
198 | if path.is_dir() { | ||
199 | return None; | ||
200 | } | ||
201 | let text = match fs::read_to_string(&path) { | ||
202 | Ok(text) => text, | ||
203 | Err(e) => { | ||
204 | log::warn!("watcher error \"{}\": {}", path.display(), e); | ||
205 | return None; | ||
206 | } | ||
207 | }; | ||
208 | WatcherChangeData::Create { path, text } | ||
209 | } | ||
210 | WatcherChange::Write(path) => { | ||
211 | let text = match fs::read_to_string(&path) { | ||
212 | Ok(text) => text, | ||
213 | Err(e) => { | ||
214 | log::warn!("watcher error \"{}\": {}", path.display(), e); | ||
215 | return None; | ||
216 | } | ||
217 | }; | ||
218 | WatcherChangeData::Write { path, text } | ||
219 | } | ||
220 | WatcherChange::Remove(path) => WatcherChangeData::Remove { path }, | ||
221 | WatcherChange::Rescan => { | ||
222 | // this should be handled by Vfs::handle_task | ||
223 | return None; | ||
224 | } | ||
225 | }; | ||
226 | Some(data) | ||
227 | } | ||
diff --git a/crates/ra_vfs/src/io/watcher.rs b/crates/ra_vfs/src/io/watcher.rs index 68bb6b692..1d7ce2136 100644 --- a/crates/ra_vfs/src/io/watcher.rs +++ b/crates/ra_vfs/src/io/watcher.rs | |||
@@ -1,118 +1,72 @@ | |||
1 | use crate::{io, RootFilter}; | 1 | use crate::{io, RootFilter, Roots, VfsRoot}; |
2 | use crossbeam_channel::Sender; | 2 | use crossbeam_channel::Sender; |
3 | use drop_bomb::DropBomb; | 3 | use drop_bomb::DropBomb; |
4 | use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher}; | 4 | use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher}; |
5 | use parking_lot::Mutex; | ||
5 | use std::{ | 6 | use std::{ |
7 | fs, | ||
6 | path::{Path, PathBuf}, | 8 | path::{Path, PathBuf}, |
7 | sync::mpsc, | 9 | sync::{mpsc, Arc}, |
8 | thread, | 10 | thread, |
9 | time::Duration, | 11 | time::Duration, |
10 | }; | 12 | }; |
11 | use walkdir::WalkDir; | 13 | use walkdir::WalkDir; |
12 | 14 | ||
13 | #[derive(Debug)] | 15 | #[derive(Debug)] |
14 | pub enum WatcherChange { | 16 | enum ChangeKind { |
15 | Create(PathBuf), | 17 | Create, |
16 | Write(PathBuf), | 18 | Write, |
17 | Remove(PathBuf), | 19 | Remove, |
18 | Rescan, | ||
19 | } | ||
20 | |||
21 | fn handle_change_event( | ||
22 | ev: DebouncedEvent, | ||
23 | sender: &Sender<io::Task>, | ||
24 | ) -> Result<(), Box<std::error::Error>> { | ||
25 | match ev { | ||
26 | DebouncedEvent::NoticeWrite(_) | ||
27 | | DebouncedEvent::NoticeRemove(_) | ||
28 | | DebouncedEvent::Chmod(_) => { | ||
29 | // ignore | ||
30 | } | ||
31 | DebouncedEvent::Rescan => { | ||
32 | sender.send(io::Task::HandleChange(WatcherChange::Rescan))?; | ||
33 | } | ||
34 | DebouncedEvent::Create(path) => { | ||
35 | sender.send(io::Task::HandleChange(WatcherChange::Create(path)))?; | ||
36 | } | ||
37 | DebouncedEvent::Write(path) => { | ||
38 | sender.send(io::Task::HandleChange(WatcherChange::Write(path)))?; | ||
39 | } | ||
40 | DebouncedEvent::Remove(path) => { | ||
41 | sender.send(io::Task::HandleChange(WatcherChange::Remove(path)))?; | ||
42 | } | ||
43 | DebouncedEvent::Rename(src, dst) => { | ||
44 | sender.send(io::Task::HandleChange(WatcherChange::Remove(src)))?; | ||
45 | sender.send(io::Task::HandleChange(WatcherChange::Create(dst)))?; | ||
46 | } | ||
47 | DebouncedEvent::Error(err, path) => { | ||
48 | // TODO should we reload the file contents? | ||
49 | log::warn!("watcher error \"{}\", {:?}", err, path); | ||
50 | } | ||
51 | } | ||
52 | Ok(()) | ||
53 | } | 20 | } |
54 | 21 | ||
55 | const WATCHER_DELAY: Duration = Duration::from_millis(250); | 22 | const WATCHER_DELAY: Duration = Duration::from_millis(250); |
56 | 23 | ||
57 | pub(crate) struct Watcher { | 24 | pub(crate) struct Watcher { |
58 | watcher: RecommendedWatcher, | ||
59 | thread: thread::JoinHandle<()>, | 25 | thread: thread::JoinHandle<()>, |
60 | bomb: DropBomb, | 26 | bomb: DropBomb, |
61 | sender: Sender<io::Task>, | 27 | watcher: Arc<Mutex<Option<RecommendedWatcher>>>, |
62 | } | 28 | } |
63 | 29 | ||
64 | impl Watcher { | 30 | impl Watcher { |
65 | pub(crate) fn start( | 31 | pub(crate) fn start( |
66 | output_sender: Sender<io::Task>, | 32 | roots: Arc<Roots>, |
33 | output_sender: Sender<io::TaskResult>, | ||
67 | ) -> Result<Watcher, Box<std::error::Error>> { | 34 | ) -> Result<Watcher, Box<std::error::Error>> { |
68 | let (input_sender, input_receiver) = mpsc::channel(); | 35 | let (input_sender, input_receiver) = mpsc::channel(); |
69 | let watcher = notify::watcher(input_sender, WATCHER_DELAY)?; | 36 | let watcher = Arc::new(Mutex::new(Some(notify::watcher( |
37 | input_sender, | ||
38 | WATCHER_DELAY, | ||
39 | )?))); | ||
70 | let sender = output_sender.clone(); | 40 | let sender = output_sender.clone(); |
41 | let watcher_clone = watcher.clone(); | ||
71 | let thread = thread::spawn(move || { | 42 | let thread = thread::spawn(move || { |
43 | let worker = WatcherWorker { | ||
44 | roots, | ||
45 | watcher: watcher_clone, | ||
46 | sender, | ||
47 | }; | ||
72 | input_receiver | 48 | input_receiver |
73 | .into_iter() | 49 | .into_iter() |
74 | // forward relevant events only | 50 | // forward relevant events only |
75 | .try_for_each(|change| handle_change_event(change, &output_sender)) | 51 | .try_for_each(|change| worker.handle_debounced_event(change)) |
76 | .unwrap() | 52 | .unwrap() |
77 | }); | 53 | }); |
78 | Ok(Watcher { | 54 | Ok(Watcher { |
79 | watcher, | ||
80 | thread, | 55 | thread, |
81 | sender, | 56 | watcher, |
82 | bomb: DropBomb::new(format!("Watcher was not shutdown")), | 57 | bomb: DropBomb::new(format!("Watcher was not shutdown")), |
83 | }) | 58 | }) |
84 | } | 59 | } |
85 | 60 | ||
86 | pub fn watch_recursive(&mut self, dir: &Path, filter: &RootFilter, emit_for_contents: bool) { | 61 | pub fn watch_root(&mut self, filter: &RootFilter) { |
87 | for res in WalkDir::new(dir) | 62 | for res in WalkDir::new(&filter.root) |
88 | .into_iter() | 63 | .into_iter() |
89 | .filter_entry(|entry| filter.can_contain(entry.path()).is_some()) | 64 | .filter_entry(filter.entry_filter()) |
90 | { | 65 | { |
91 | match res { | 66 | match res { |
92 | Ok(entry) => { | 67 | Ok(entry) => { |
93 | if entry.path().is_dir() { | 68 | if entry.path().is_dir() { |
94 | match self | 69 | watch_one(self.watcher.as_ref(), entry.path()); |
95 | .watcher | ||
96 | .watch(entry.path(), RecursiveMode::NonRecursive) | ||
97 | { | ||
98 | Ok(()) => log::debug!("watching \"{}\"", entry.path().display()), | ||
99 | Err(e) => { | ||
100 | log::warn!("could not watch \"{}\": {}", entry.path().display(), e) | ||
101 | } | ||
102 | } | ||
103 | } else { | ||
104 | if emit_for_contents && entry.depth() > 0 { | ||
105 | // emit only for files otherwise we will cause watch_recursive to be called again with a dir that we are already watching | ||
106 | // emit as create because we haven't seen it yet | ||
107 | if let Err(e) = | ||
108 | self.sender | ||
109 | .send(io::Task::HandleChange(WatcherChange::Create( | ||
110 | entry.path().to_path_buf(), | ||
111 | ))) | ||
112 | { | ||
113 | log::warn!("watcher error: {}", e) | ||
114 | } | ||
115 | } | ||
116 | } | 70 | } |
117 | } | 71 | } |
118 | Err(e) => log::warn!("watcher error: {}", e), | 72 | Err(e) => log::warn!("watcher error: {}", e), |
@@ -122,7 +76,7 @@ impl Watcher { | |||
122 | 76 | ||
123 | pub fn shutdown(mut self) -> thread::Result<()> { | 77 | pub fn shutdown(mut self) -> thread::Result<()> { |
124 | self.bomb.defuse(); | 78 | self.bomb.defuse(); |
125 | drop(self.watcher); | 79 | drop(self.watcher.lock().take()); |
126 | let res = self.thread.join(); | 80 | let res = self.thread.join(); |
127 | match &res { | 81 | match &res { |
128 | Ok(()) => log::info!("... Watcher terminated with ok"), | 82 | Ok(()) => log::info!("... Watcher terminated with ok"), |
@@ -131,3 +85,116 @@ impl Watcher { | |||
131 | res | 85 | res |
132 | } | 86 | } |
133 | } | 87 | } |
88 | |||
89 | struct WatcherWorker { | ||
90 | watcher: Arc<Mutex<Option<RecommendedWatcher>>>, | ||
91 | roots: Arc<Roots>, | ||
92 | sender: Sender<io::TaskResult>, | ||
93 | } | ||
94 | |||
95 | impl 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(|entry| filter.can_contain(entry.path()).is_some()) | ||
176 | { | ||
177 | match res { | ||
178 | Ok(entry) => { | ||
179 | if entry.path().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 | |||
193 | fn 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 cba3a463a..661892f8a 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs | |||
@@ -28,22 +28,25 @@ use crossbeam_channel::Receiver; | |||
28 | use ra_arena::{impl_arena_id, Arena, RawId}; | 28 | use ra_arena::{impl_arena_id, Arena, RawId}; |
29 | use relative_path::{Component, RelativePath, RelativePathBuf}; | 29 | use relative_path::{Component, RelativePath, RelativePathBuf}; |
30 | use rustc_hash::{FxHashMap, FxHashSet}; | 30 | use rustc_hash::{FxHashMap, FxHashSet}; |
31 | use walkdir::DirEntry; | ||
31 | 32 | ||
32 | pub use crate::io::TaskResult as VfsTask; | 33 | pub use crate::io::TaskResult as VfsTask; |
33 | use io::{Task, TaskResult, WatcherChange, WatcherChangeData, Worker}; | 34 | use io::{TaskResult, Worker}; |
34 | 35 | ||
35 | /// `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 |
36 | /// 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. |
37 | pub(crate) struct RootFilter { | 38 | pub(crate) struct RootFilter { |
38 | root: PathBuf, | 39 | root: PathBuf, |
39 | filter: fn(&Path, &RelativePath) -> bool, | 40 | filter: fn(&Path, &RelativePath) -> bool, |
41 | excluded_dirs: Vec<PathBuf>, | ||
40 | } | 42 | } |
41 | 43 | ||
42 | impl RootFilter { | 44 | impl RootFilter { |
43 | fn new(root: PathBuf) -> RootFilter { | 45 | fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootFilter { |
44 | RootFilter { | 46 | RootFilter { |
45 | root, | 47 | root, |
46 | filter: default_filter, | 48 | filter: default_filter, |
49 | excluded_dirs, | ||
47 | } | 50 | } |
48 | } | 51 | } |
49 | /// 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 |
@@ -56,6 +59,17 @@ impl RootFilter { | |||
56 | } | 59 | } |
57 | Some(rel_path) | 60 | Some(rel_path) |
58 | } | 61 | } |
62 | |||
63 | pub(crate) fn entry_filter<'a>(&'a self) -> impl FnMut(&DirEntry) -> bool + 'a { | ||
64 | move |entry: &DirEntry| { | ||
65 | if entry.path().is_dir() && self.excluded_dirs.iter().any(|it| it == entry.path()) { | ||
66 | // do not walk nested roots | ||
67 | false | ||
68 | } else { | ||
69 | self.can_contain(entry.path()).is_some() | ||
70 | } | ||
71 | } | ||
72 | } | ||
59 | } | 73 | } |
60 | 74 | ||
61 | pub(crate) fn default_filter(path: &Path, rel_path: &RelativePath) -> bool { | 75 | pub(crate) fn default_filter(path: &Path, rel_path: &RelativePath) -> bool { |
@@ -94,10 +108,22 @@ pub(crate) struct Roots { | |||
94 | } | 108 | } |
95 | 109 | ||
96 | impl Roots { | 110 | impl Roots { |
97 | pub(crate) fn new() -> Roots { | 111 | pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots { |
98 | Roots { | 112 | let mut roots = Arena::default(); |
99 | roots: Arena::default(), | 113 | // A hack to make nesting work. |
114 | paths.sort_by_key(|it| Reverse(it.as_os_str().len())); | ||
115 | for (i, path) in paths.iter().enumerate() { | ||
116 | let nested_roots = paths[..i] | ||
117 | .iter() | ||
118 | .filter(|it| it.starts_with(path)) | ||
119 | .map(|it| it.clone()) | ||
120 | .collect::<Vec<_>>(); | ||
121 | |||
122 | let root_filter = Arc::new(RootFilter::new(path.clone(), nested_roots)); | ||
123 | |||
124 | roots.alloc(root_filter.clone()); | ||
100 | } | 125 | } |
126 | Roots { roots } | ||
101 | } | 127 | } |
102 | pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> { | 128 | pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> { |
103 | self.roots | 129 | self.roots |
@@ -135,36 +161,22 @@ impl fmt::Debug for Vfs { | |||
135 | 161 | ||
136 | impl Vfs { | 162 | impl Vfs { |
137 | pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { | 163 | pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { |
138 | let mut root_paths = roots; | 164 | let roots = Arc::new(Roots::new(roots)); |
139 | let worker = io::Worker::start(); | 165 | let worker = io::Worker::start(roots.clone()); |
140 | |||
141 | let mut roots = Roots::new(); | ||
142 | let mut root2files = FxHashMap::default(); | 166 | let mut root2files = FxHashMap::default(); |
143 | 167 | ||
144 | // A hack to make nesting work. | 168 | for (root, filter) in roots.iter() { |
145 | root_paths.sort_by_key(|it| Reverse(it.as_os_str().len())); | ||
146 | for (i, path) in root_paths.iter().enumerate() { | ||
147 | let root_filter = Arc::new(RootFilter::new(path.clone())); | ||
148 | |||
149 | let root = roots.alloc(root_filter.clone()); | ||
150 | root2files.insert(root, Default::default()); | 169 | root2files.insert(root, Default::default()); |
151 | 170 | worker | |
152 | let nested_roots = root_paths[..i] | 171 | .sender() |
153 | .iter() | 172 | .send(io::Task::AddRoot { |
154 | .filter(|it| it.starts_with(path)) | 173 | root, |
155 | .map(|it| it.clone()) | 174 | filter: filter.clone(), |
156 | .collect::<Vec<_>>(); | 175 | }) |
157 | 176 | .unwrap(); | |
158 | let task = io::Task::AddRoot { | ||
159 | root, | ||
160 | path: path.clone(), | ||
161 | root_filter, | ||
162 | nested_roots, | ||
163 | }; | ||
164 | worker.sender().send(task).unwrap(); | ||
165 | } | 177 | } |
166 | let res = Vfs { | 178 | let res = Vfs { |
167 | roots: Arc::new(roots), | 179 | roots, |
168 | files: Arena::default(), | 180 | files: Arena::default(), |
169 | root2files, | 181 | root2files, |
170 | worker, | 182 | worker, |
@@ -225,90 +237,46 @@ impl Vfs { | |||
225 | 237 | ||
226 | pub fn handle_task(&mut self, task: io::TaskResult) { | 238 | pub fn handle_task(&mut self, task: io::TaskResult) { |
227 | match task { | 239 | match task { |
228 | TaskResult::AddRoot(task) => { | 240 | TaskResult::BulkLoadRoot { root, files } => { |
229 | let mut files = Vec::new(); | 241 | let mut cur_files = Vec::new(); |
230 | // While we were scanning the root in the backgound, a file might have | 242 | // While we were scanning the root in the backgound, a file might have |
231 | // been open in the editor, so we need to account for that. | 243 | // been open in the editor, so we need to account for that. |
232 | let exising = self.root2files[&task.root] | 244 | let exising = self.root2files[&root] |
233 | .iter() | 245 | .iter() |
234 | .map(|&file| (self.files[file].path.clone(), file)) | 246 | .map(|&file| (self.files[file].path.clone(), file)) |
235 | .collect::<FxHashMap<_, _>>(); | 247 | .collect::<FxHashMap<_, _>>(); |
236 | for (path, text) in task.files { | 248 | for (path, text) in files { |
237 | if let Some(&file) = exising.get(&path) { | 249 | if let Some(&file) = exising.get(&path) { |
238 | let text = Arc::clone(&self.files[file].text); | 250 | let text = Arc::clone(&self.files[file].text); |
239 | files.push((file, path, text)); | 251 | cur_files.push((file, path, text)); |
240 | continue; | 252 | continue; |
241 | } | 253 | } |
242 | let text = Arc::new(text); | 254 | let text = Arc::new(text); |
243 | let file = self.add_file(task.root, path.clone(), Arc::clone(&text), false); | 255 | let file = self.add_file(root, path.clone(), Arc::clone(&text), false); |
244 | files.push((file, path, text)); | 256 | cur_files.push((file, path, text)); |
245 | } | 257 | } |
246 | 258 | ||
247 | let change = VfsChange::AddRoot { | 259 | let change = VfsChange::AddRoot { |
248 | root: task.root, | 260 | root, |
249 | files, | 261 | files: cur_files, |
250 | }; | 262 | }; |
251 | self.pending_changes.push(change); | 263 | self.pending_changes.push(change); |
252 | } | 264 | } |
253 | TaskResult::HandleChange(change) => match &change { | 265 | TaskResult::AddSingleFile { root, path, text } => { |
254 | WatcherChange::Create(path) if path.is_dir() => { | 266 | self.do_add_file(root, path, text, false); |
255 | if let Some((root, _path, _file)) = self.find_root(&path) { | 267 | } |
256 | let root_filter = self.roots[root].clone(); | 268 | TaskResult::ChangeSingleFile { root, path, text } => { |
257 | self.worker | 269 | if let Some(file) = self.find_file(root, &path) { |
258 | .sender() | 270 | self.do_change_file(file, text, false); |
259 | .send(Task::Watch { | 271 | } else { |
260 | dir: path.to_path_buf(), | 272 | self.do_add_file(root, path, text, false); |
261 | root_filter, | ||
262 | }) | ||
263 | .unwrap() | ||
264 | } | ||
265 | } | ||
266 | WatcherChange::Create(path) | ||
267 | | WatcherChange::Remove(path) | ||
268 | | WatcherChange::Write(path) => { | ||
269 | if self.should_handle_change(&path) { | ||
270 | self.worker.sender().send(Task::LoadChange(change)).unwrap() | ||
271 | } | ||
272 | } | ||
273 | WatcherChange::Rescan => { | ||
274 | // TODO we should reload all files | ||
275 | } | ||
276 | }, | ||
277 | TaskResult::LoadChange(change) => match change { | ||
278 | WatcherChangeData::Create { path, text } | ||
279 | | WatcherChangeData::Write { path, text } => { | ||
280 | if let Some((root, path, file)) = self.find_root(&path) { | ||
281 | if let Some(file) = file { | ||
282 | self.do_change_file(file, text, false); | ||
283 | } else { | ||
284 | self.do_add_file(root, path, text, false); | ||
285 | } | ||
286 | } | ||
287 | } | ||
288 | WatcherChangeData::Remove { path } => { | ||
289 | if let Some((root, path, file)) = self.find_root(&path) { | ||
290 | if let Some(file) = file { | ||
291 | self.do_remove_file(root, path, file, false); | ||
292 | } | ||
293 | } | ||
294 | } | 273 | } |
295 | }, | 274 | } |
296 | } | 275 | TaskResult::RemoveSingleFile { root, path } => { |
297 | } | 276 | if let Some(file) = self.find_file(root, &path) { |
298 | 277 | self.do_remove_file(root, path, file, false); | |
299 | fn should_handle_change(&self, path: &Path) -> bool { | ||
300 | if let Some((_root, _rel_path, file)) = self.find_root(&path) { | ||
301 | if let Some(file) = file { | ||
302 | if self.files[file].is_overlayed { | ||
303 | // file is overlayed | ||
304 | log::debug!("skipping overlayed \"{}\"", path.display()); | ||
305 | return false; | ||
306 | } | 278 | } |
307 | } | 279 | } |
308 | true | ||
309 | } else { | ||
310 | // file doesn't belong to any root | ||
311 | false | ||
312 | } | 280 | } |
313 | } | 281 | } |
314 | 282 | ||
@@ -434,11 +402,15 @@ impl Vfs { | |||
434 | 402 | ||
435 | fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> { | 403 | fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<VfsFile>)> { |
436 | let (root, path) = self.roots.find(&path)?; | 404 | let (root, path) = self.roots.find(&path)?; |
437 | let file = self.root2files[&root] | 405 | let file = self.find_file(root, &path); |
406 | Some((root, path, file)) | ||
407 | } | ||
408 | |||
409 | fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> { | ||
410 | self.root2files[&root] | ||
438 | .iter() | 411 | .iter() |
439 | .map(|&it| it) | 412 | .map(|&it| it) |
440 | .find(|&file| self.files[file].path == path); | 413 | .find(|&file| self.files[file].path == path) |
441 | Some((root, path, file)) | ||
442 | } | 414 | } |
443 | } | 415 | } |
444 | 416 | ||
diff --git a/crates/ra_vfs/tests/vfs.rs b/crates/ra_vfs/tests/vfs.rs index bf44e97c5..8562c56b9 100644 --- a/crates/ra_vfs/tests/vfs.rs +++ b/crates/ra_vfs/tests/vfs.rs | |||
@@ -75,27 +75,31 @@ fn test_vfs_works() -> std::io::Result<()> { | |||
75 | } | 75 | } |
76 | 76 | ||
77 | fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap(); | 77 | fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap(); |
78 | // 2 tasks per change, HandleChange and then LoadChange | 78 | process_tasks(&mut vfs, 1); |
79 | process_tasks(&mut vfs, 2); | ||
80 | assert_match!( | 79 | assert_match!( |
81 | vfs.commit_changes().as_slice(), | 80 | vfs.commit_changes().as_slice(), |
82 | [VfsChange::ChangeFile { text, .. }], | 81 | [VfsChange::ChangeFile { text, .. }], |
83 | assert_eq!(text.as_str(), "quux") | 82 | assert_eq!(text.as_str(), "quux") |
84 | ); | 83 | ); |
85 | 84 | ||
86 | vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), "m".to_string()); | 85 | vfs.add_file_overlay(&dir.path().join("a/b/baz.rs"), "m".to_string()); |
87 | assert_match!( | 86 | assert_match!( |
88 | vfs.commit_changes().as_slice(), | 87 | vfs.commit_changes().as_slice(), |
89 | [VfsChange::ChangeFile { text, .. }], | 88 | [VfsChange::ChangeFile { text, .. }], |
90 | assert_eq!(text.as_str(), "m") | 89 | assert_eq!(text.as_str(), "m") |
91 | ); | 90 | ); |
92 | 91 | ||
92 | // changing file on disk while overlayed doesn't generate a VfsChange | ||
93 | fs::write(&dir.path().join("a/b/baz.rs"), "corge").unwrap(); | ||
94 | process_tasks(&mut vfs, 1); | ||
95 | assert_match!(vfs.commit_changes().as_slice(), []); | ||
96 | |||
93 | // removing overlay restores data on disk | 97 | // removing overlay restores data on disk |
94 | vfs.remove_file_overlay(&dir.path().join("a/b/baz.rs")); | 98 | vfs.remove_file_overlay(&dir.path().join("a/b/baz.rs")); |
95 | assert_match!( | 99 | assert_match!( |
96 | vfs.commit_changes().as_slice(), | 100 | vfs.commit_changes().as_slice(), |
97 | [VfsChange::ChangeFile { text, .. }], | 101 | [VfsChange::ChangeFile { text, .. }], |
98 | assert_eq!(text.as_str(), "quux") | 102 | assert_eq!(text.as_str(), "corge") |
99 | ); | 103 | ); |
100 | 104 | ||
101 | vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), "spam".to_string()); | 105 | vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), "spam".to_string()); |
@@ -117,7 +121,7 @@ fn test_vfs_works() -> std::io::Result<()> { | |||
117 | 121 | ||
118 | fs::create_dir_all(dir.path().join("a/sub1/sub2")).unwrap(); | 122 | fs::create_dir_all(dir.path().join("a/sub1/sub2")).unwrap(); |
119 | fs::write(dir.path().join("a/sub1/sub2/new.rs"), "new hello").unwrap(); | 123 | fs::write(dir.path().join("a/sub1/sub2/new.rs"), "new hello").unwrap(); |
120 | process_tasks(&mut vfs, 3); | 124 | process_tasks(&mut vfs, 1); |
121 | assert_match!( | 125 | assert_match!( |
122 | vfs.commit_changes().as_slice(), | 126 | vfs.commit_changes().as_slice(), |
123 | [VfsChange::AddFile { text, path, .. }], | 127 | [VfsChange::AddFile { text, path, .. }], |
@@ -132,7 +136,7 @@ fn test_vfs_works() -> std::io::Result<()> { | |||
132 | &dir.path().join("a/sub1/sub2/new1.rs"), | 136 | &dir.path().join("a/sub1/sub2/new1.rs"), |
133 | ) | 137 | ) |
134 | .unwrap(); | 138 | .unwrap(); |
135 | process_tasks(&mut vfs, 4); | 139 | process_tasks(&mut vfs, 2); |
136 | assert_match!( | 140 | assert_match!( |
137 | vfs.commit_changes().as_slice(), | 141 | vfs.commit_changes().as_slice(), |
138 | [VfsChange::RemoveFile { | 142 | [VfsChange::RemoveFile { |
@@ -150,17 +154,16 @@ fn test_vfs_works() -> std::io::Result<()> { | |||
150 | ); | 154 | ); |
151 | 155 | ||
152 | fs::remove_file(&dir.path().join("a/sub1/sub2/new1.rs")).unwrap(); | 156 | fs::remove_file(&dir.path().join("a/sub1/sub2/new1.rs")).unwrap(); |
153 | process_tasks(&mut vfs, 2); | 157 | process_tasks(&mut vfs, 1); |
154 | assert_match!( | 158 | assert_match!( |
155 | vfs.commit_changes().as_slice(), | 159 | vfs.commit_changes().as_slice(), |
156 | [VfsChange::RemoveFile { path, .. }], | 160 | [VfsChange::RemoveFile { path, .. }], |
157 | assert_eq!(path, "sub1/sub2/new1.rs") | 161 | assert_eq!(path, "sub1/sub2/new1.rs") |
158 | ); | 162 | ); |
159 | 163 | ||
160 | fs::create_dir_all(dir.path().join("a/target")).unwrap(); | ||
161 | // should be ignored | 164 | // should be ignored |
165 | fs::create_dir_all(dir.path().join("a/target")).unwrap(); | ||
162 | fs::write(&dir.path().join("a/target/new.rs"), "ignore me").unwrap(); | 166 | fs::write(&dir.path().join("a/target/new.rs"), "ignore me").unwrap(); |
163 | process_tasks(&mut vfs, 1); // 1 task because no LoadChange will happen, just HandleChange for dir creation | ||
164 | 167 | ||
165 | assert_match!( | 168 | assert_match!( |
166 | vfs.task_receiver().try_recv(), | 169 | vfs.task_receiver().try_recv(), |