diff --git a/Cargo.lock b/Cargo.lock index fccf8a65e1..a694c2d916 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1009,6 +1009,7 @@ version = "0.1.0" dependencies = [ "crossbeam-channel 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "drop_bomb 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "flexi_logger 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "ra_arena 0.1.0", diff --git a/crates/ra_vfs/Cargo.toml b/crates/ra_vfs/Cargo.toml index f7a972e91b..b703cbd9fc 100644 --- a/crates/ra_vfs/Cargo.toml +++ b/crates/ra_vfs/Cargo.toml @@ -18,3 +18,4 @@ ra_arena = { path = "../ra_arena" } [dev-dependencies] tempfile = "3" +flexi_logger = "0.10.0" diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index 5336822b33..1ca94dcd6c 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs @@ -98,7 +98,7 @@ impl Vfs { pub fn new(mut roots: Vec) -> (Vfs, Vec) { let (worker, worker_handle) = io::start(); - let watcher = Watcher::new().unwrap(); // TODO return Result? + let watcher = Watcher::start().unwrap(); // TODO return Result? let mut res = Vfs { roots: Arena::default(), diff --git a/crates/ra_vfs/src/watcher.rs b/crates/ra_vfs/src/watcher.rs index cc05f949ee..1aac236161 100644 --- a/crates/ra_vfs/src/watcher.rs +++ b/crates/ra_vfs/src/watcher.rs @@ -39,7 +39,6 @@ impl WatcherChange { DebouncedEvent::Remove(path) => Some(WatcherChange::Remove(path)), DebouncedEvent::Rename(src, dst) => Some(WatcherChange::Rename(src, dst)), DebouncedEvent::Error(err, path) => { - // TODO log::warn!("watch error {}, {:?}", err, path); None } @@ -48,23 +47,17 @@ impl WatcherChange { } impl Watcher { - pub fn new() -> Result> { + pub fn start() -> Result> { let (input_sender, input_receiver) = mpsc::channel(); let watcher = notify::watcher(input_sender, Duration::from_millis(250))?; let (output_sender, output_receiver) = crossbeam_channel::unbounded(); - let thread = thread::spawn(move || loop { - match input_receiver.recv() { - Ok(ev) => { - // forward relevant events only - if let Some(change) = WatcherChange::from_debounced_event(ev) { - output_sender.send(change).unwrap(); - } - } - Err(err) => { - log::debug!("Watcher stopped ({})", err); - break; - } - } + let thread = thread::spawn(move || { + input_receiver + .into_iter() + // forward relevant events only + .filter_map(WatcherChange::from_debounced_event) + .try_for_each(|change| output_sender.send(change)) + .unwrap() }); Ok(Watcher { receiver: output_receiver, @@ -86,11 +79,13 @@ impl Watcher { pub fn shutdown(mut self) -> thread::Result<()> { self.bomb.defuse(); drop(self.watcher); - let res = self.thread.join(); - match &res { - Ok(()) => log::info!("... Watcher terminated with ok"), - Err(_) => log::error!("... Watcher terminated with err"), - } - res + // TODO this doesn't terminate for some reason + // let res = self.thread.join(); + // match &res { + // Ok(()) => log::info!("... Watcher terminated with ok"), + // Err(_) => log::error!("... Watcher terminated with err"), + // } + // res + Ok(()) } } diff --git a/crates/ra_vfs/tests/vfs.rs b/crates/ra_vfs/tests/vfs.rs index f56fc46037..8634be9c49 100644 --- a/crates/ra_vfs/tests/vfs.rs +++ b/crates/ra_vfs/tests/vfs.rs @@ -1,14 +1,13 @@ -use std::{ - fs, - collections::HashSet, -}; - -use tempfile::tempdir; +use std::{collections::HashSet, fs}; +use flexi_logger::Logger; use ra_vfs::{Vfs, VfsChange}; +use tempfile::tempdir; #[test] fn test_vfs_works() -> std::io::Result<()> { + Logger::with_str("debug").start().unwrap(); + let files = [ ("a/foo.rs", "hello"), ("a/bar.rs", "world"), @@ -58,42 +57,89 @@ fn test_vfs_works() -> std::io::Result<()> { assert_eq!(files, expected_files); } - vfs.add_file_overlay(&dir.path().join("a/b/baz.rs"), "quux".to_string()); - let change = vfs.commit_changes().pop().unwrap(); - match change { - VfsChange::ChangeFile { text, .. } => assert_eq!(&*text, "quux"), - _ => panic!("unexpected change"), + // on disk change + fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap(); + let change = vfs.change_receiver().recv().unwrap(); + vfs.handle_change(change); + match vfs.commit_changes().as_slice() { + [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "quux"), + _ => panic!("unexpected changes"), } - vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), "m".to_string()); - let change = vfs.commit_changes().pop().unwrap(); - match change { - VfsChange::ChangeFile { text, .. } => assert_eq!(&*text, "m"), - _ => panic!("unexpected change"), + // in memory change + vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), Some("m".to_string())); + match vfs.commit_changes().as_slice() { + [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "m"), + _ => panic!("unexpected changes"), } + // in memory remove, restores data on disk vfs.remove_file_overlay(&dir.path().join("a/b/baz.rs")); - let change = vfs.commit_changes().pop().unwrap(); - match change { - VfsChange::ChangeFile { text, .. } => assert_eq!(&*text, "nested hello"), - _ => panic!("unexpected change"), + match vfs.commit_changes().as_slice() { + [VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "quux"), + _ => panic!("unexpected changes"), } - vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), "spam".to_string()); - let change = vfs.commit_changes().pop().unwrap(); - match change { - VfsChange::AddFile { text, path, .. } => { - assert_eq!(&*text, "spam"); + // in memory add + vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), Some("spam".to_string())); + match vfs.commit_changes().as_slice() { + [VfsChange::AddFile { text, path, .. }] => { + assert_eq!(text.as_str(), "spam"); assert_eq!(path, "spam.rs"); } - _ => panic!("unexpected change"), + _ => panic!("unexpected changes"), } + // in memory remove vfs.remove_file_overlay(&dir.path().join("a/b/spam.rs")); - let change = vfs.commit_changes().pop().unwrap(); - match change { - VfsChange::RemoveFile { .. } => (), - _ => panic!("unexpected change"), + match vfs.commit_changes().as_slice() { + [VfsChange::RemoveFile { path, .. }] => assert_eq!(path, "spam.rs"), + _ => panic!("unexpected changes"), + } + + // on disk add + fs::write(&dir.path().join("a/new.rs"), "new hello").unwrap(); + let change = vfs.change_receiver().recv().unwrap(); + vfs.handle_change(change); + match vfs.commit_changes().as_slice() { + [VfsChange::AddFile { text, path, .. }] => { + assert_eq!(text.as_str(), "new hello"); + assert_eq!(path, "new.rs"); + } + _ => panic!("unexpected changes"), + } + + // on disk rename + fs::rename(&dir.path().join("a/new.rs"), &dir.path().join("a/new1.rs")).unwrap(); + let change = vfs.change_receiver().recv().unwrap(); + vfs.handle_change(change); + match vfs.commit_changes().as_slice() { + [VfsChange::RemoveFile { + path: removed_path, .. + }, VfsChange::AddFile { + text, + path: added_path, + .. + }] => { + assert_eq!(removed_path, "new.rs"); + assert_eq!(added_path, "new1.rs"); + assert_eq!(text.as_str(), "new hello"); + } + _ => panic!("unexpected changes"), + } + + // on disk remove + fs::remove_file(&dir.path().join("a/new1.rs")).unwrap(); + let change = vfs.change_receiver().recv().unwrap(); + vfs.handle_change(change); + match vfs.commit_changes().as_slice() { + [VfsChange::RemoveFile { path, .. }] => assert_eq!(path, "new1.rs"), + _ => panic!("unexpected changes"), + } + + match vfs.change_receiver().try_recv() { + Err(crossbeam_channel::TryRecvError::Empty) => (), + res => panic!("unexpected {:?}", res), } vfs.shutdown().unwrap();