Skip to content

Commit

Permalink
Introduce cross-platform file-watching (#6855)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
aminya authored Jan 29, 2024
1 parent b29f45e commit 1313402
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 12 deletions.
72 changes: 71 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion crates/fs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"] }

Expand Down
59 changes: 57 additions & 2 deletions crates/fs/src/fs.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -48,11 +57,13 @@ pub trait Fs: Send + Sync {
&self,
path: &Path,
) -> Result<Pin<Box<dyn Send + Stream<Item = Result<PathBuf>>>>>;

async fn watch(
&self,
path: &Path,
latency: Duration,
) -> Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>;
) -> Pin<Box<dyn Send + Stream<Item = Vec<Event>>>>;

fn open_repo(&self, abs_dot_git: &Path) -> Option<Arc<Mutex<dyn GitRepository>>>;
fn is_fake(&self) -> bool;
#[cfg(any(test, feature = "test-support"))]
Expand Down Expand Up @@ -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<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>> {
) -> Pin<Box<dyn Send + Stream<Item = Vec<Event>>>> {
let (tx, rx) = smol::channel::unbounded();
let (stream, handle) = EventStream::new(&[path], latency);
std::thread::spawn(move || {
Expand All @@ -267,6 +279,35 @@ impl Fs for RealFs {
})))
}

#[cfg(not(target_os = "macos"))]
async fn watch(
&self,
path: &Path,
latency: Duration,
) -> Pin<Box<dyn Send + Stream<Item = Vec<Event>>>> {
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<Arc<Mutex<dyn GitRepository>>> {
LibGitRepository::open(&dotgit_path)
.log_err()
Expand All @@ -284,6 +325,20 @@ impl Fs for RealFs {
}
}

#[cfg(target_os = "macos")]
pub fn fs_events_paths(events: Vec<Event>) -> Vec<PathBuf> {
events.into_iter().map(|event| event.path).collect()
}

#[cfg(not(target_os = "macos"))]
pub fn fs_events_paths(events: Vec<Event>) -> Vec<PathBuf> {
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.
Expand Down
15 changes: 7 additions & 8 deletions crates/project/src/worktree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3221,10 +3221,7 @@ impl BackgroundScanner {
}
}

async fn run(
&mut self,
mut fs_events_rx: Pin<Box<dyn Send + Stream<Item = Vec<fsevent::Event>>>>,
) {
async fn run(&mut self, mut fs_events_rx: Pin<Box<dyn Send + Stream<Item = Vec<fs::Event>>>>) {
use futures::FutureExt as _;

// Populate ignores above the root.
Expand Down Expand Up @@ -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::<Vec<_>>();
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;
}
Expand Down Expand Up @@ -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::<Vec<_>>();
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;
}
Expand Down

0 comments on commit 1313402

Please sign in to comment.