From 1313402a6bfd3f508aec8b4a8d89b0b5a826261b Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Mon, 29 Jan 2024 12:18:10 -0800 Subject: [PATCH] Introduce cross-platform file-watching (#6855) This adds cross-platform file-watching via the [Notify](https://github.com/notify-rs/notify) crate. The previous fs-events implementation is now only used on MacOS, and on other platforms Notify is used. The watching function interface is the same. Related to #5391 #5395 #5394. Release Notes: - N/A --- Cargo.lock | 72 +++++++++++++++++++++++++++++++++- crates/fs/Cargo.toml | 7 +++- crates/fs/src/fs.rs | 59 +++++++++++++++++++++++++++- crates/project/src/worktree.rs | 15 ++++--- 4 files changed, 141 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3d009697249fab629e16db2a3528e4d9969378b..8a141a31cb48853fda0d4e81119d58a5a51f8f78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2737,6 +2737,7 @@ dependencies = [ "lazy_static", "libc", "log", + "notify", "parking_lot 0.11.2", "regex", "rope", @@ -2756,7 +2757,7 @@ name = "fsevent" version = "2.0.2" dependencies = [ "bitflags 1.3.2", - "fsevent-sys", + "fsevent-sys 3.1.0", "parking_lot 0.11.2", "tempfile", ] @@ -2770,6 +2771,15 @@ dependencies = [ "libc", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -3509,6 +3519,26 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "install_cli" version = "0.1.0" @@ -3726,6 +3756,26 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kurbo" version = "0.8.3" @@ -4281,6 +4331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -4534,6 +4585,25 @@ dependencies = [ "util", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.1", + "crossbeam-channel", + "filetime", + "fsevent-sys 4.1.0", + "inotify", + "kqueue", + "libc", + "log", + "mio 0.8.8", + "walkdir", + "windows-sys 0.48.0", +] + [[package]] name = "ntapi" version = "0.3.7" diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 599b1678ca1609440eea78697b736a6280639b1a..4f7293b1a7854369d8cf2b0dc52c99c8da051e27 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -20,7 +20,6 @@ anyhow.workspace = true async-trait.workspace = true futures.workspace = true tempfile = "3" -fsevent = { path = "../fsevent" } lazy_static.workspace = true parking_lot.workspace = true smol.workspace = true @@ -35,6 +34,12 @@ time.workspace = true gpui = { path = "../gpui", optional = true} +[target.'cfg(target_os = "macos")'.dependencies] +fsevent = { path = "../fsevent" } + +[target.'cfg(not(target_os = "macos"))'.dependencies] +notify = "6.1.1" + [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index b010394ec2478b750462bb8f4ab50d47b6e8834e..edd3da101cb8ad9ed48304d6860c8c1f75db31d3 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -1,7 +1,16 @@ pub mod repository; use anyhow::{anyhow, Result}; +#[cfg(target_os = "macos")] +pub use fsevent::Event; +#[cfg(target_os = "macos")] use fsevent::EventStream; + +#[cfg(not(target_os = "macos"))] +pub use notify::Event; +#[cfg(not(target_os = "macos"))] +use notify::{Config, Watcher}; + use futures::{future::BoxFuture, Stream, StreamExt}; use git2::Repository as LibGitRepository; use parking_lot::Mutex; @@ -48,11 +57,13 @@ pub trait Fs: Send + Sync { &self, path: &Path, ) -> Result>>>>; + async fn watch( &self, path: &Path, latency: Duration, - ) -> Pin>>>; + ) -> Pin>>>; + fn open_repo(&self, abs_dot_git: &Path) -> Option>>; fn is_fake(&self) -> bool; #[cfg(any(test, feature = "test-support"))] @@ -251,11 +262,12 @@ impl Fs for RealFs { Ok(Box::pin(result)) } + #[cfg(target_os = "macos")] async fn watch( &self, path: &Path, latency: Duration, - ) -> Pin>>> { + ) -> Pin>>> { let (tx, rx) = smol::channel::unbounded(); let (stream, handle) = EventStream::new(&[path], latency); std::thread::spawn(move || { @@ -267,6 +279,35 @@ impl Fs for RealFs { }))) } + #[cfg(not(target_os = "macos"))] + async fn watch( + &self, + path: &Path, + latency: Duration, + ) -> Pin>>> { + let (tx, rx) = smol::channel::unbounded(); + + let mut watcher = notify::recommended_watcher(move |res| match res { + Ok(event) => { + let _ = tx.try_send(vec![event]); + } + Err(err) => { + eprintln!("watch error: {:?}", err); + } + }) + .unwrap(); + + watcher + .configure(Config::default().with_poll_interval(latency)) + .unwrap(); + + watcher + .watch(path, notify::RecursiveMode::Recursive) + .unwrap(); + + Box::pin(rx) + } + fn open_repo(&self, dotgit_path: &Path) -> Option>> { LibGitRepository::open(&dotgit_path) .log_err() @@ -284,6 +325,20 @@ impl Fs for RealFs { } } +#[cfg(target_os = "macos")] +pub fn fs_events_paths(events: Vec) -> Vec { + events.into_iter().map(|event| event.path).collect() +} + +#[cfg(not(target_os = "macos"))] +pub fn fs_events_paths(events: Vec) -> Vec { + events + .into_iter() + .map(|event| event.paths.into_iter()) + .flatten() + .collect() +} + #[cfg(any(test, feature = "test-support"))] pub struct FakeFs { // Use an unfair lock to ensure tests are deterministic. diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 84bb0aa7a65c051a60d6e083f993172400d244a4..6ba9a9d026a86898e763130ab4b0a24a40685468 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3221,10 +3221,7 @@ impl BackgroundScanner { } } - async fn run( - &mut self, - mut fs_events_rx: Pin>>>, - ) { + async fn run(&mut self, mut fs_events_rx: Pin>>>) { use futures::FutureExt as _; // Populate ignores above the root. @@ -3271,9 +3268,10 @@ impl BackgroundScanner { // have the previous state loaded yet. self.phase = BackgroundScannerPhase::EventsReceivedDuringInitialScan; if let Poll::Ready(Some(events)) = futures::poll!(fs_events_rx.next()) { - let mut paths = events.into_iter().map(|e| e.path).collect::>(); + let mut paths = fs::fs_events_paths(events); + while let Poll::Ready(Some(more_events)) = futures::poll!(fs_events_rx.next()) { - paths.extend(more_events.into_iter().map(|e| e.path)); + paths.extend(fs::fs_events_paths(more_events)); } self.process_events(paths).await; } @@ -3312,9 +3310,10 @@ impl BackgroundScanner { events = fs_events_rx.next().fuse() => { let Some(events) = events else { break }; - let mut paths = events.into_iter().map(|e| e.path).collect::>(); + let mut paths = fs::fs_events_paths(events); + while let Poll::Ready(Some(more_events)) = futures::poll!(fs_events_rx.next()) { - paths.extend(more_events.into_iter().map(|e| e.path)); + paths.extend(fs::fs_events_paths(more_events)); } self.process_events(paths.clone()).await; }