diff options
Diffstat (limited to 'crates/ra_vfs')
-rw-r--r-- | crates/ra_vfs/src/io.rs | 57 | ||||
-rw-r--r-- | crates/ra_vfs/src/lib.rs | 81 | ||||
-rw-r--r-- | crates/ra_vfs/src/watcher.rs | 62 | ||||
-rw-r--r-- | crates/ra_vfs/tests/vfs.rs | 22 |
4 files changed, 134 insertions, 88 deletions
diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs index 80328ad18..79dea5dc7 100644 --- a/crates/ra_vfs/src/io.rs +++ b/crates/ra_vfs/src/io.rs | |||
@@ -10,17 +10,47 @@ use relative_path::RelativePathBuf; | |||
10 | 10 | ||
11 | use crate::{VfsRoot, has_rs_extension}; | 11 | use crate::{VfsRoot, has_rs_extension}; |
12 | 12 | ||
13 | pub(crate) struct Task { | 13 | pub(crate) enum Task { |
14 | pub(crate) root: VfsRoot, | 14 | AddRoot { |
15 | pub(crate) path: PathBuf, | 15 | root: VfsRoot, |
16 | pub(crate) filter: Box<Fn(&DirEntry) -> bool + Send>, | 16 | path: PathBuf, |
17 | filter: Box<Fn(&DirEntry) -> bool + Send>, | ||
18 | }, | ||
19 | WatcherChange(crate::watcher::WatcherChange), | ||
17 | } | 20 | } |
18 | 21 | ||
19 | pub struct TaskResult { | 22 | #[derive(Debug)] |
23 | pub struct AddRootResult { | ||
20 | pub(crate) root: VfsRoot, | 24 | pub(crate) root: VfsRoot, |
21 | pub(crate) files: Vec<(RelativePathBuf, String)>, | 25 | pub(crate) files: Vec<(RelativePathBuf, String)>, |
22 | } | 26 | } |
23 | 27 | ||
28 | #[derive(Debug)] | ||
29 | pub enum WatcherChangeResult { | ||
30 | Create { | ||
31 | path: PathBuf, | ||
32 | text: String, | ||
33 | }, | ||
34 | Write { | ||
35 | path: PathBuf, | ||
36 | text: String, | ||
37 | }, | ||
38 | Remove { | ||
39 | path: PathBuf, | ||
40 | }, | ||
41 | // can this be replaced and use Remove and Create instead? | ||
42 | Rename { | ||
43 | src: PathBuf, | ||
44 | dst: PathBuf, | ||
45 | text: String, | ||
46 | }, | ||
47 | } | ||
48 | |||
49 | pub enum TaskResult { | ||
50 | AddRoot(AddRootResult), | ||
51 | WatcherChange(WatcherChangeResult), | ||
52 | } | ||
53 | |||
24 | impl fmt::Debug for TaskResult { | 54 | impl fmt::Debug for TaskResult { |
25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | 55 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
26 | f.write_str("TaskResult { ... }") | 56 | f.write_str("TaskResult { ... }") |
@@ -40,11 +70,18 @@ pub(crate) fn start() -> (Worker, WorkerHandle) { | |||
40 | } | 70 | } |
41 | 71 | ||
42 | fn handle_task(task: Task) -> TaskResult { | 72 | fn handle_task(task: Task) -> TaskResult { |
43 | let Task { root, path, filter } = task; | 73 | match task { |
44 | log::debug!("loading {} ...", path.as_path().display()); | 74 | Task::AddRoot { root, path, filter } => { |
45 | let files = load_root(path.as_path(), &*filter); | 75 | log::debug!("loading {} ...", path.as_path().display()); |
46 | log::debug!("... loaded {}", path.as_path().display()); | 76 | let files = load_root(path.as_path(), &*filter); |
47 | TaskResult { root, files } | 77 | log::debug!("... loaded {}", path.as_path().display()); |
78 | TaskResult::AddRoot(AddRootResult { root, files }) | ||
79 | } | ||
80 | Task::WatcherChange(change) => { | ||
81 | // TODO | ||
82 | unimplemented!() | ||
83 | } | ||
84 | } | ||
48 | } | 85 | } |
49 | 86 | ||
50 | fn load_root(root: &Path, filter: &dyn Fn(&DirEntry) -> bool) -> Vec<(RelativePathBuf, String)> { | 87 | fn load_root(root: &Path, filter: &dyn Fn(&DirEntry) -> bool) -> Vec<(RelativePathBuf, String)> { |
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index 1ca94dcd6..889ed788d 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs | |||
@@ -60,7 +60,7 @@ impl RootFilter { | |||
60 | } | 60 | } |
61 | } | 61 | } |
62 | 62 | ||
63 | fn has_rs_extension(p: &Path) -> bool { | 63 | pub(crate) fn has_rs_extension(p: &Path) -> bool { |
64 | p.extension() == Some(OsStr::new("rs")) | 64 | p.extension() == Some(OsStr::new("rs")) |
65 | } | 65 | } |
66 | 66 | ||
@@ -98,7 +98,7 @@ impl Vfs { | |||
98 | pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { | 98 | pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) { |
99 | let (worker, worker_handle) = io::start(); | 99 | let (worker, worker_handle) = io::start(); |
100 | 100 | ||
101 | let watcher = Watcher::start().unwrap(); // TODO return Result? | 101 | let watcher = Watcher::start(worker.inp.clone()).unwrap(); // TODO return Result? |
102 | 102 | ||
103 | let mut res = Vfs { | 103 | let mut res = Vfs { |
104 | roots: Arena::default(), | 104 | roots: Arena::default(), |
@@ -127,7 +127,7 @@ impl Vfs { | |||
127 | nested.iter().all(|it| it != entry.path()) | 127 | nested.iter().all(|it| it != entry.path()) |
128 | } | 128 | } |
129 | }; | 129 | }; |
130 | let task = io::Task { | 130 | let task = io::Task::AddRoot { |
131 | root, | 131 | root, |
132 | path: path.clone(), | 132 | path: path.clone(), |
133 | filter: Box::new(filter), | 133 | filter: Box::new(filter), |
@@ -188,58 +188,43 @@ impl Vfs { | |||
188 | &self.worker.out | 188 | &self.worker.out |
189 | } | 189 | } |
190 | 190 | ||
191 | pub fn change_receiver(&self) -> &Receiver<WatcherChange> { | ||
192 | &self.watcher.change_receiver() | ||
193 | } | ||
194 | |||
195 | pub fn handle_task(&mut self, task: io::TaskResult) { | 191 | pub fn handle_task(&mut self, task: io::TaskResult) { |
196 | let mut files = Vec::new(); | 192 | match task { |
197 | // While we were scanning the root in the backgound, a file might have | 193 | io::TaskResult::AddRoot(task) => { |
198 | // been open in the editor, so we need to account for that. | 194 | let mut files = Vec::new(); |
199 | let exising = self.root2files[&task.root] | 195 | // While we were scanning the root in the backgound, a file might have |
200 | .iter() | 196 | // been open in the editor, so we need to account for that. |
201 | .map(|&file| (self.files[file].path.clone(), file)) | 197 | let exising = self.root2files[&task.root] |
202 | .collect::<FxHashMap<_, _>>(); | 198 | .iter() |
203 | for (path, text) in task.files { | 199 | .map(|&file| (self.files[file].path.clone(), file)) |
204 | if let Some(&file) = exising.get(&path) { | 200 | .collect::<FxHashMap<_, _>>(); |
205 | let text = Arc::clone(&self.files[file].text); | 201 | for (path, text) in task.files { |
206 | files.push((file, path, text)); | 202 | if let Some(&file) = exising.get(&path) { |
207 | continue; | 203 | let text = Arc::clone(&self.files[file].text); |
208 | } | 204 | files.push((file, path, text)); |
209 | let text = Arc::new(text); | 205 | continue; |
210 | let file = self.add_file(task.root, path.clone(), Arc::clone(&text)); | 206 | } |
211 | files.push((file, path, text)); | 207 | let text = Arc::new(text); |
212 | } | 208 | let file = self.add_file(task.root, path.clone(), Arc::clone(&text)); |
213 | 209 | files.push((file, path, text)); | |
214 | let change = VfsChange::AddRoot { | 210 | } |
215 | root: task.root, | ||
216 | files, | ||
217 | }; | ||
218 | self.pending_changes.push(change); | ||
219 | } | ||
220 | 211 | ||
221 | pub fn handle_change(&mut self, change: WatcherChange) { | 212 | let change = VfsChange::AddRoot { |
222 | match change { | 213 | root: task.root, |
223 | WatcherChange::Create(path) => { | 214 | files, |
224 | self.add_file_overlay(&path, None); | 215 | }; |
225 | } | 216 | self.pending_changes.push(change); |
226 | WatcherChange::Remove(path) => { | ||
227 | self.remove_file_overlay(&path); | ||
228 | } | ||
229 | WatcherChange::Rename(src, dst) => { | ||
230 | self.remove_file_overlay(&src); | ||
231 | self.add_file_overlay(&dst, None); | ||
232 | } | 217 | } |
233 | WatcherChange::Write(path) => { | 218 | io::TaskResult::WatcherChange(change) => { |
234 | self.change_file_overlay(&path, None); | 219 | // TODO |
220 | unimplemented!() | ||
235 | } | 221 | } |
236 | } | 222 | } |
237 | } | 223 | } |
238 | 224 | ||
239 | pub fn add_file_overlay(&mut self, path: &Path, text: Option<String>) -> Option<VfsFile> { | 225 | pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> { |
240 | let mut res = None; | 226 | let mut res = None; |
241 | if let Some((root, rel_path, file)) = self.find_root(path) { | 227 | if let Some((root, rel_path, file)) = self.find_root(path) { |
242 | let text = text.unwrap_or_else(|| fs::read_to_string(&path).unwrap_or_default()); | ||
243 | let text = Arc::new(text); | 228 | let text = Arc::new(text); |
244 | let change = if let Some(file) = file { | 229 | let change = if let Some(file) = file { |
245 | res = Some(file); | 230 | res = Some(file); |
@@ -260,10 +245,8 @@ impl Vfs { | |||
260 | res | 245 | res |
261 | } | 246 | } |
262 | 247 | ||
263 | pub fn change_file_overlay(&mut self, path: &Path, new_text: Option<String>) { | 248 | pub fn change_file_overlay(&mut self, path: &Path, new_text: String) { |
264 | if let Some((_root, _path, file)) = self.find_root(path) { | 249 | if let Some((_root, _path, file)) = self.find_root(path) { |
265 | let new_text = | ||
266 | new_text.unwrap_or_else(|| fs::read_to_string(&path).unwrap_or_default()); | ||
267 | let file = file.expect("can't change a file which wasn't added"); | 250 | let file = file.expect("can't change a file which wasn't added"); |
268 | let text = Arc::new(new_text); | 251 | let text = Arc::new(new_text); |
269 | self.change_file(file, Arc::clone(&text)); | 252 | self.change_file(file, Arc::clone(&text)); |
diff --git a/crates/ra_vfs/src/watcher.rs b/crates/ra_vfs/src/watcher.rs index 1aac23616..a6d0496c0 100644 --- a/crates/ra_vfs/src/watcher.rs +++ b/crates/ra_vfs/src/watcher.rs | |||
@@ -5,12 +5,12 @@ use std::{ | |||
5 | time::Duration, | 5 | time::Duration, |
6 | }; | 6 | }; |
7 | 7 | ||
8 | use crossbeam_channel::Receiver; | 8 | use crossbeam_channel::Sender; |
9 | use drop_bomb::DropBomb; | 9 | use drop_bomb::DropBomb; |
10 | use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher}; | 10 | use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher}; |
11 | use crate::{has_rs_extension, io}; | ||
11 | 12 | ||
12 | pub struct Watcher { | 13 | pub struct Watcher { |
13 | receiver: Receiver<WatcherChange>, | ||
14 | watcher: RecommendedWatcher, | 14 | watcher: RecommendedWatcher, |
15 | thread: thread::JoinHandle<()>, | 15 | thread: thread::JoinHandle<()>, |
16 | bomb: DropBomb, | 16 | bomb: DropBomb, |
@@ -21,24 +21,54 @@ pub enum WatcherChange { | |||
21 | Create(PathBuf), | 21 | Create(PathBuf), |
22 | Write(PathBuf), | 22 | Write(PathBuf), |
23 | Remove(PathBuf), | 23 | Remove(PathBuf), |
24 | // can this be replaced and use Remove and Create instead? | ||
24 | Rename(PathBuf, PathBuf), | 25 | Rename(PathBuf, PathBuf), |
25 | } | 26 | } |
26 | 27 | ||
27 | impl WatcherChange { | 28 | impl WatcherChange { |
28 | fn from_debounced_event(ev: DebouncedEvent) -> Option<WatcherChange> { | 29 | fn try_from_debounced_event(ev: DebouncedEvent) -> Option<WatcherChange> { |
29 | match ev { | 30 | match ev { |
30 | DebouncedEvent::NoticeWrite(_) | 31 | DebouncedEvent::NoticeWrite(_) |
31 | | DebouncedEvent::NoticeRemove(_) | 32 | | DebouncedEvent::NoticeRemove(_) |
32 | | DebouncedEvent::Chmod(_) | 33 | | DebouncedEvent::Chmod(_) => { |
33 | | DebouncedEvent::Rescan => { | ||
34 | // ignore | 34 | // ignore |
35 | None | 35 | None |
36 | } | 36 | } |
37 | DebouncedEvent::Create(path) => Some(WatcherChange::Create(path)), | 37 | DebouncedEvent::Rescan => { |
38 | DebouncedEvent::Write(path) => Some(WatcherChange::Write(path)), | 38 | // TODO should we rescan the root? |
39 | DebouncedEvent::Remove(path) => Some(WatcherChange::Remove(path)), | 39 | None |
40 | DebouncedEvent::Rename(src, dst) => Some(WatcherChange::Rename(src, dst)), | 40 | } |
41 | DebouncedEvent::Create(path) => { | ||
42 | if has_rs_extension(&path) { | ||
43 | Some(WatcherChange::Create(path)) | ||
44 | } else { | ||
45 | None | ||
46 | } | ||
47 | } | ||
48 | DebouncedEvent::Write(path) => { | ||
49 | if has_rs_extension(&path) { | ||
50 | Some(WatcherChange::Write(path)) | ||
51 | } else { | ||
52 | None | ||
53 | } | ||
54 | } | ||
55 | DebouncedEvent::Remove(path) => { | ||
56 | if has_rs_extension(&path) { | ||
57 | Some(WatcherChange::Remove(path)) | ||
58 | } else { | ||
59 | None | ||
60 | } | ||
61 | } | ||
62 | DebouncedEvent::Rename(src, dst) => { | ||
63 | match (has_rs_extension(&src), has_rs_extension(&dst)) { | ||
64 | (true, true) => Some(WatcherChange::Rename(src, dst)), | ||
65 | (true, false) => Some(WatcherChange::Remove(src)), | ||
66 | (false, true) => Some(WatcherChange::Create(dst)), | ||
67 | (false, false) => None, | ||
68 | } | ||
69 | } | ||
41 | DebouncedEvent::Error(err, path) => { | 70 | DebouncedEvent::Error(err, path) => { |
71 | // TODO should we reload the file contents? | ||
42 | log::warn!("watch error {}, {:?}", err, path); | 72 | log::warn!("watch error {}, {:?}", err, path); |
43 | None | 73 | None |
44 | } | 74 | } |
@@ -47,20 +77,20 @@ impl WatcherChange { | |||
47 | } | 77 | } |
48 | 78 | ||
49 | impl Watcher { | 79 | impl Watcher { |
50 | pub fn start() -> Result<Watcher, Box<std::error::Error>> { | 80 | pub(crate) fn start( |
81 | output_sender: Sender<io::Task>, | ||
82 | ) -> Result<Watcher, Box<std::error::Error>> { | ||
51 | let (input_sender, input_receiver) = mpsc::channel(); | 83 | let (input_sender, input_receiver) = mpsc::channel(); |
52 | let watcher = notify::watcher(input_sender, Duration::from_millis(250))?; | 84 | let watcher = notify::watcher(input_sender, Duration::from_millis(250))?; |
53 | let (output_sender, output_receiver) = crossbeam_channel::unbounded(); | ||
54 | let thread = thread::spawn(move || { | 85 | let thread = thread::spawn(move || { |
55 | input_receiver | 86 | input_receiver |
56 | .into_iter() | 87 | .into_iter() |
57 | // forward relevant events only | 88 | // forward relevant events only |
58 | .filter_map(WatcherChange::from_debounced_event) | 89 | .filter_map(WatcherChange::try_from_debounced_event) |
59 | .try_for_each(|change| output_sender.send(change)) | 90 | .try_for_each(|change| output_sender.send(io::Task::WatcherChange(change))) |
60 | .unwrap() | 91 | .unwrap() |
61 | }); | 92 | }); |
62 | Ok(Watcher { | 93 | Ok(Watcher { |
63 | receiver: output_receiver, | ||
64 | watcher, | 94 | watcher, |
65 | thread, | 95 | thread, |
66 | bomb: DropBomb::new(format!("Watcher was not shutdown")), | 96 | bomb: DropBomb::new(format!("Watcher was not shutdown")), |
@@ -72,10 +102,6 @@ impl Watcher { | |||
72 | Ok(()) | 102 | Ok(()) |
73 | } | 103 | } |
74 | 104 | ||
75 | pub fn change_receiver(&self) -> &Receiver<WatcherChange> { | ||
76 | &self.receiver | ||
77 | } | ||
78 | |||
79 | pub fn shutdown(mut self) -> thread::Result<()> { | 105 | pub fn shutdown(mut self) -> thread::Result<()> { |
80 | self.bomb.defuse(); | 106 | self.bomb.defuse(); |
81 | drop(self.watcher); | 107 | drop(self.watcher); |
diff --git a/crates/ra_vfs/tests/vfs.rs b/crates/ra_vfs/tests/vfs.rs index 8634be9c4..21d5633b1 100644 --- a/crates/ra_vfs/tests/vfs.rs +++ b/crates/ra_vfs/tests/vfs.rs | |||
@@ -59,15 +59,15 @@ fn test_vfs_works() -> std::io::Result<()> { | |||
59 | 59 | ||
60 | // on disk change | 60 | // on disk change |
61 | fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap(); | 61 | fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap(); |
62 | let change = vfs.change_receiver().recv().unwrap(); | 62 | let task = vfs.task_receiver().recv().unwrap(); |
63 | vfs.handle_change(change); | 63 | vfs.handle_task(task); |
64 | match vfs.commit_changes().as_slice() { | 64 | match vfs.commit_changes().as_slice() { |
65 | [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "quux"), | 65 | [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "quux"), |
66 | _ => panic!("unexpected changes"), | 66 | _ => panic!("unexpected changes"), |
67 | } | 67 | } |
68 | 68 | ||
69 | // in memory change | 69 | // in memory change |
70 | vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), Some("m".to_string())); | 70 | vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), "m".to_string()); |
71 | match vfs.commit_changes().as_slice() { | 71 | match vfs.commit_changes().as_slice() { |
72 | [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "m"), | 72 | [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "m"), |
73 | _ => panic!("unexpected changes"), | 73 | _ => panic!("unexpected changes"), |
@@ -81,7 +81,7 @@ fn test_vfs_works() -> std::io::Result<()> { | |||
81 | } | 81 | } |
82 | 82 | ||
83 | // in memory add | 83 | // in memory add |
84 | vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), Some("spam".to_string())); | 84 | vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), "spam".to_string()); |
85 | match vfs.commit_changes().as_slice() { | 85 | match vfs.commit_changes().as_slice() { |
86 | [VfsChange::AddFile { text, path, .. }] => { | 86 | [VfsChange::AddFile { text, path, .. }] => { |
87 | assert_eq!(text.as_str(), "spam"); | 87 | assert_eq!(text.as_str(), "spam"); |
@@ -99,8 +99,8 @@ fn test_vfs_works() -> std::io::Result<()> { | |||
99 | 99 | ||
100 | // on disk add | 100 | // on disk add |
101 | fs::write(&dir.path().join("a/new.rs"), "new hello").unwrap(); | 101 | fs::write(&dir.path().join("a/new.rs"), "new hello").unwrap(); |
102 | let change = vfs.change_receiver().recv().unwrap(); | 102 | let task = vfs.task_receiver().recv().unwrap(); |
103 | vfs.handle_change(change); | 103 | vfs.handle_task(task); |
104 | match vfs.commit_changes().as_slice() { | 104 | match vfs.commit_changes().as_slice() { |
105 | [VfsChange::AddFile { text, path, .. }] => { | 105 | [VfsChange::AddFile { text, path, .. }] => { |
106 | assert_eq!(text.as_str(), "new hello"); | 106 | assert_eq!(text.as_str(), "new hello"); |
@@ -111,8 +111,8 @@ fn test_vfs_works() -> std::io::Result<()> { | |||
111 | 111 | ||
112 | // on disk rename | 112 | // on disk rename |
113 | fs::rename(&dir.path().join("a/new.rs"), &dir.path().join("a/new1.rs")).unwrap(); | 113 | fs::rename(&dir.path().join("a/new.rs"), &dir.path().join("a/new1.rs")).unwrap(); |
114 | let change = vfs.change_receiver().recv().unwrap(); | 114 | let task = vfs.task_receiver().recv().unwrap(); |
115 | vfs.handle_change(change); | 115 | vfs.handle_task(task); |
116 | match vfs.commit_changes().as_slice() { | 116 | match vfs.commit_changes().as_slice() { |
117 | [VfsChange::RemoveFile { | 117 | [VfsChange::RemoveFile { |
118 | path: removed_path, .. | 118 | path: removed_path, .. |
@@ -130,14 +130,14 @@ fn test_vfs_works() -> std::io::Result<()> { | |||
130 | 130 | ||
131 | // on disk remove | 131 | // on disk remove |
132 | fs::remove_file(&dir.path().join("a/new1.rs")).unwrap(); | 132 | fs::remove_file(&dir.path().join("a/new1.rs")).unwrap(); |
133 | let change = vfs.change_receiver().recv().unwrap(); | 133 | let task = vfs.task_receiver().recv().unwrap(); |
134 | vfs.handle_change(change); | 134 | vfs.handle_task(task); |
135 | match vfs.commit_changes().as_slice() { | 135 | match vfs.commit_changes().as_slice() { |
136 | [VfsChange::RemoveFile { path, .. }] => assert_eq!(path, "new1.rs"), | 136 | [VfsChange::RemoveFile { path, .. }] => assert_eq!(path, "new1.rs"), |
137 | _ => panic!("unexpected changes"), | 137 | _ => panic!("unexpected changes"), |
138 | } | 138 | } |
139 | 139 | ||
140 | match vfs.change_receiver().try_recv() { | 140 | match vfs.task_receiver().try_recv() { |
141 | Err(crossbeam_channel::TryRecvError::Empty) => (), | 141 | Err(crossbeam_channel::TryRecvError::Empty) => (), |
142 | res => panic!("unexpected {:?}", res), | 142 | res => panic!("unexpected {:?}", res), |
143 | } | 143 | } |