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:
parent
d58f006498
commit
d3fc00d5a0
4 changed files with 56 additions and 72 deletions
|
@ -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
|
||||
|
|
|
@ -5,12 +5,12 @@ use util::ResultExt;
|
|||
|
||||
use crate::{PathEvent, PathEventKind, Watcher};
|
||||
|
||||
pub struct LinuxWatcher {
|
||||
pub struct FsWatcher {
|
||||
tx: smol::channel::Sender<()>,
|
||||
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
|
||||
}
|
||||
|
||||
impl LinuxWatcher {
|
||||
impl FsWatcher {
|
||||
pub fn new(
|
||||
tx: smol::channel::Sender<()>,
|
||||
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
|
||||
|
@ -22,7 +22,7 @@ impl LinuxWatcher {
|
|||
}
|
||||
}
|
||||
|
||||
impl Watcher for LinuxWatcher {
|
||||
impl Watcher for FsWatcher {
|
||||
fn add(&self, path: &std::path::Path) -> gpui::Result<()> {
|
||||
let root_path = path.to_path_buf();
|
||||
|
||||
|
@ -69,7 +69,7 @@ impl Watcher for LinuxWatcher {
|
|||
})?;
|
||||
|
||||
global(|g| {
|
||||
g.inotify
|
||||
g.watcher
|
||||
.lock()
|
||||
.watch(path, notify::RecursiveMode::NonRecursive)
|
||||
})??;
|
||||
|
@ -79,16 +79,18 @@ impl Watcher for LinuxWatcher {
|
|||
|
||||
fn remove(&self, path: &std::path::Path) -> gpui::Result<()> {
|
||||
use notify::Watcher;
|
||||
Ok(global(|w| w.inotify.lock().unwatch(path))??)
|
||||
Ok(global(|w| w.watcher.lock().unwatch(path))??)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlobalWatcher {
|
||||
// two mutexes because calling inotify.add triggers an inotify.event, which needs watchers.
|
||||
// two mutexes because calling watcher.add triggers an watcher.event, which needs watchers.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(super) inotify: Mutex<notify::INotifyWatcher>,
|
||||
pub(super) watcher: Mutex<notify::INotifyWatcher>,
|
||||
#[cfg(target_os = "freebsd")]
|
||||
pub(super) inotify: Mutex<notify::KqueueWatcher>,
|
||||
pub(super) watcher: Mutex<notify::KqueueWatcher>,
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(super) watcher: Mutex<notify::ReadDirectoryChangesWatcher>,
|
||||
pub(super) watchers: Mutex<Vec<Box<dyn Fn(¬ify::Event) + Send + Sync>>>,
|
||||
}
|
||||
|
||||
|
@ -98,7 +100,8 @@ impl GlobalWatcher {
|
|||
}
|
||||
}
|
||||
|
||||
static INOTIFY_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> = OnceLock::new();
|
||||
static FS_WATCHER_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> =
|
||||
OnceLock::new();
|
||||
|
||||
fn handle_event(event: Result<notify::Event, notify::Error>) {
|
||||
let Some(event) = event.log_err() else { return };
|
||||
|
@ -111,9 +114,9 @@ fn handle_event(event: Result<notify::Event, notify::Error>) {
|
|||
}
|
||||
|
||||
pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
|
||||
let result = INOTIFY_INSTANCE.get_or_init(|| {
|
||||
let result = FS_WATCHER_INSTANCE.get_or_init(|| {
|
||||
notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
|
||||
inotify: Mutex::new(file_watcher),
|
||||
watcher: Mutex::new(file_watcher),
|
||||
watchers: Default::default(),
|
||||
})
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue