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
This commit is contained in:
@@ -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"] }
|
||||
|
||||
|
||||
+57
-2
@@ -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<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"))]
|
||||
@@ -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 || {
|
||||
@@ -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()
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user