windows: Fix fs watch when file doesn't exist or is a symlink (#22660)

Closes #22659

More context can be found in attached issue.

This is specific to Windows:

1. Add parent directory watching for fs watch when the file doesn't
exist. For example, when Zed is first launched and `settings.json` isn't
there.
2. Add proper symlink handling for fs watch. For example, when
`settings.json` is a symlink.

This is exactly same as how we handle it on Linux.

Release Notes:

- Fixed an issue where items on the Welcome page could not be toggled on
Windows, either on first launch or when `settings.json` is a symlink.
This commit is contained in:
tims 2025-01-07 23:50:22 +05:30 committed by GitHub
parent d58f006498
commit d3fc00d5a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 56 additions and 72 deletions

View file

@ -1,8 +1,8 @@
#[cfg(target_os = "macos")]
mod mac_watcher;
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub mod linux_watcher;
#[cfg(not(target_os = "macos"))]
pub mod fs_watcher;
use anyhow::{anyhow, Result};
use git::GitHostingProviderRegistry;
@ -700,7 +700,7 @@ impl Fs for RealFs {
)
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
#[cfg(not(target_os = "macos"))]
async fn watch(
&self,
path: &Path,
@ -710,10 +710,11 @@ impl Fs for RealFs {
Arc<dyn Watcher>,
) {
use parking_lot::Mutex;
use util::paths::SanitizedPath;
let (tx, rx) = smol::channel::unbounded();
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
let watcher = Arc::new(linux_watcher::LinuxWatcher::new(tx, pending_paths.clone()));
let watcher = Arc::new(fs_watcher::FsWatcher::new(tx, pending_paths.clone()));
if watcher.add(path).is_err() {
// If the path doesn't exist yet (e.g. settings.json), watch the parent dir to learn when it's created.
@ -731,7 +732,7 @@ impl Fs for RealFs {
if let Some(parent) = path.parent() {
target = parent.join(target);
if let Ok(canonical) = self.canonicalize(&target).await {
target = canonical;
target = SanitizedPath::from(canonical).as_path().to_path_buf();
}
}
}
@ -758,56 +759,6 @@ impl Fs for RealFs {
)
}
#[cfg(target_os = "windows")]
async fn watch(
&self,
path: &Path,
_latency: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
) {
use notify::{EventKind, Watcher};
let (tx, rx) = smol::channel::unbounded();
let mut file_watcher = notify::recommended_watcher({
let tx = tx.clone();
move |event: Result<notify::Event, _>| {
if let Some(event) = event.log_err() {
let kind = match event.kind {
EventKind::Create(_) => Some(PathEventKind::Created),
EventKind::Modify(_) => Some(PathEventKind::Changed),
EventKind::Remove(_) => Some(PathEventKind::Removed),
_ => None,
};
tx.try_send(
event
.paths
.into_iter()
.map(|path| PathEvent { path, kind })
.collect::<Vec<_>>(),
)
.ok();
}
}
})
.expect("Could not start file watcher");
file_watcher
.watch(path, notify::RecursiveMode::Recursive)
.log_err();
(
Box::pin(rx.chain(futures::stream::once(async move {
drop(file_watcher);
vec![]
}))),
Arc::new(RealWatcher {}),
)
}
fn open_repo(&self, dotgit_path: &Path) -> Option<Arc<dyn GitRepository>> {
// with libgit2, we can open git repo from an existing work dir
// https://libgit2.org/docs/reference/main/repository/git_repository_open.html