Compare commits

...

2 Commits

Author SHA1 Message Date
Nia Espera
ee3087090f sorta works but Not 2025-12-03 16:49:19 +01:00
Nia Espera
c9d6a2ca24 borked 2025-12-03 13:03:40 +01:00
8 changed files with 287 additions and 23 deletions

9
Cargo.lock generated
View File

@@ -6412,6 +6412,7 @@ dependencies = [
"git",
"gpui",
"ignore",
"inotify 0.11.0",
"is_executable",
"libc",
"log",
@@ -8256,8 +8257,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [
"bitflags 2.9.4",
"futures-core",
"inotify-sys",
"libc",
"tokio",
]
[[package]]
@@ -10448,7 +10451,8 @@ dependencies = [
[[package]]
name = "notify"
version = "8.2.0"
source = "git+https://github.com/zed-industries/notify.git?rev=b4588b2e5aee68f4c0e100f140e808cbce7b1419#b4588b2e5aee68f4c0e100f140e808cbce7b1419"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [
"bitflags 2.9.4",
"fsevent-sys 4.1.0",
@@ -10476,7 +10480,8 @@ dependencies = [
[[package]]
name = "notify-types"
version = "2.0.0"
source = "git+https://github.com/zed-industries/notify.git?rev=b4588b2e5aee68f4c0e100f140e808cbce7b1419#b4588b2e5aee68f4c0e100f140e808cbce7b1419"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
[[package]]
name = "now"

View File

@@ -775,8 +775,8 @@ features = [
]
[patch.crates-io]
notify = { git = "https://github.com/zed-industries/notify.git", rev = "b4588b2e5aee68f4c0e100f140e808cbce7b1419" }
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "b4588b2e5aee68f4c0e100f140e808cbce7b1419" }
#notify = { path = "../notify/notify" }
#notify-types = { path = "../notify/notify-types" }
windows-capture = { git = "https://github.com/zed-industries/windows-capture.git", rev = "f0d6c1b6691db75461b732f6d5ff56eed002eeb9" }
calloop = { git = "https://github.com/zed-industries/calloop" }

View File

@@ -40,9 +40,12 @@ fsevent.workspace = true
objc.workspace = true
cocoa = "0.26"
[target.'cfg(not(target_os = "macos"))'.dependencies]
[target.'cfg(not(any(target_os = "macos", target_os = "linux")))'.dependencies]
notify = "8.2.0"
[target.'cfg(target_os = "linux")'.dependencies]
inotify = "0.11.0"
[target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true

View File

@@ -1,7 +1,10 @@
#[cfg(target_os = "macos")]
mod mac_watcher;
#[cfg(not(target_os = "macos"))]
#[cfg(target_os = "linux")]
mod linux_watcher;
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
pub mod fs_watcher;
use parking_lot::Mutex;
@@ -420,7 +423,7 @@ impl RealFs {
pub fn new(git_binary_path: Option<PathBuf>, executor: BackgroundExecutor) -> Self {
Self {
bundled_git_binary_path: git_binary_path,
executor,
executor: executor.clone(),
next_job_id: Arc::new(AtomicUsize::new(0)),
job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
}
@@ -1003,7 +1006,43 @@ impl Fs for RealFs {
)
}
#[cfg(not(target_os = "macos"))]
#[cfg(target_os = "linux")]
async fn watch(
&self,
path: &Path,
_: Duration,
) -> (
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
Arc<dyn Watcher>,
) {
use util::{ResultExt as _, paths::SanitizedPath};
let (watcher, stream) = linux_watcher::LinuxWatcher::init(self.executor.clone());
watcher.add(path).unwrap();
// Check if path is a symlink and follow the target parent
if let Some(mut target) = self.read_link(path).await.ok() {
dbg!("we got a symlink");
log::trace!("watch symlink {path:?} -> {target:?}");
// Check if symlink target is relative path, if so make it absolute
if target.is_relative()
&& let Some(parent) = path.parent()
{
target = parent.join(target);
if let Ok(canonical) = self.canonicalize(&target).await {
target = SanitizedPath::new(&canonical).as_path().to_path_buf();
}
}
watcher.add(&target).ok();
if let Some(parent) = target.parent() {
watcher.add(parent).log_err();
}
}
(stream, Arc::new(watcher) as _)
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
async fn watch(
&self,
path: &Path,
@@ -1016,6 +1055,7 @@ impl Fs for RealFs {
let (tx, rx) = smol::channel::unbounded();
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
let watcher = Arc::new(fs_watcher::FsWatcher::new(tx, pending_paths.clone()));
// If the path doesn't exist yet (e.g. settings.json), watch the parent dir to learn when it's created.
@@ -3378,12 +3418,7 @@ mod tests {
async fn test_realfs_atomic_write(executor: BackgroundExecutor) {
// With the file handle still open, the file should be replaced
// https://github.com/zed-industries/zed/issues/30054
let fs = RealFs {
bundled_git_binary_path: None,
executor,
next_job_id: Arc::new(AtomicUsize::new(0)),
job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
};
let fs = RealFs::new(None, executor);
let temp_dir = TempDir::new().unwrap();
let file_to_be_replaced = temp_dir.path().join("file.txt");
let mut file = std::fs::File::create_new(&file_to_be_replaced).unwrap();
@@ -3398,12 +3433,7 @@ mod tests {
#[gpui::test]
async fn test_realfs_atomic_write_non_existing_file(executor: BackgroundExecutor) {
let fs = RealFs {
bundled_git_binary_path: None,
executor,
next_job_id: Arc::new(AtomicUsize::new(0)),
job_event_subscribers: Arc::new(Mutex::new(Vec::new())),
};
let fs = RealFs::new(None, executor);
let temp_dir = TempDir::new().unwrap();
let file_to_be_replaced = temp_dir.path().join("file.txt");
smol::block_on(fs.atomic_write(file_to_be_replaced.clone(), "Hello".into())).unwrap();

View File

@@ -1,4 +1,6 @@
use notify::EventKind;
#[cfg(target_os = "linux")]
use notify::{INotifyWatcher, Watcher};
use parking_lot::Mutex;
use std::{
collections::{BTreeMap, HashMap},
@@ -249,6 +251,11 @@ fn handle_event(event: Result<notify::Event, notify::Error>) {
pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
let result = FS_WATCHER_INSTANCE.get_or_init(|| {
#[cfg(target_os = "linux")]
{
let watcher = INotifyWatcher::new(event_handler, config);
}
#[cfg(not(linux))]
notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
state: Mutex::new(WatcherState {
watchers: Default::default(),

View File

@@ -0,0 +1,218 @@
use std::{
path::Path,
pin::Pin,
sync::{Arc, OnceLock},
};
use crate::{PathEvent, Watcher};
use anyhow::Result;
use collections::HashMap;
use futures::Stream;
use gpui::{BackgroundExecutor, Task};
use inotify::{Inotify, WatchMask};
use smol::{
channel::{Receiver, Sender},
future::block_on,
};
pub struct LinuxWatcher {
inotify: InotifyWatcher,
poller: PollWatcher,
//task: Task<Result<Never>>,
}
#[derive(Debug)]
enum WatcherStateUpdate {
NewPath(Arc<Path>),
RemPath(Arc<Path>),
}
struct InotifyWatcher {
tx: Sender<WatcherStateUpdate>,
}
struct PollWatcher {}
enum Never {}
impl LinuxWatcher {
pub fn init(
executor: BackgroundExecutor,
) -> (Self, Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>) {
let (tx_paths, rx_paths) = smol::channel::unbounded();
let (tx_events, rx_events) = smol::channel::unbounded();
let task = executor.spawn::<Result<Never>>(async move {
const WATCH_MASK: WatchMask = WatchMask::CREATE
.union(WatchMask::CLOSE_WRITE)
.union(WatchMask::DELETE)
.union(WatchMask::DELETE_SELF)
.union(WatchMask::MODIFY)
.union(WatchMask::MOVE)
.union(WatchMask::MOVE_SELF); // todo: fix this
let mut buf = Box::<[u8; 1024]>::new_uninit();
unsafe {
buf.as_mut_ptr().write_bytes(0, 1);
}
let mut buf = unsafe { buf.assume_init() };
let mut watcher = Inotify::init()?;
let mut path_map = HashMap::default();
loop {
//dbg!();
for upd in (0..).map_while(|_| rx_paths.try_recv().ok()) {
//dbg!(&upd);
match upd {
WatcherStateUpdate::NewPath(p) => {
//dbg!();
let wd = watcher.watches().add(p.clone(), WATCH_MASK)?;
//dbg!();
path_map.insert(wd, p.clone());
tx_events
.send(vec![PathEvent {
path: p.to_path_buf(),
kind: None,
}])
.await?;
//dbg!();
log::info!("Watching {p:?}");
}
WatcherStateUpdate::RemPath(p) => {
if let Some((wd, _)) = path_map.iter().find(|(_, v)| **v == p) {
let wd = wd.clone();
path_map.remove(&wd);
watcher.watches().remove(wd)?;
//dbg!();
log::info!("No longer watching {p:?}");
}
tx_events
.send(vec![PathEvent {
path: p.to_path_buf(),
kind: None,
}])
.await?;
//dbg!();
}
};
}
//dbg!();
let mut evts = vec![];
// todo: skip wouldblock errors
match watcher.read_events(&mut *buf) {
Ok(raw_events) => {
for evt in raw_events {
log::info!("Got event {evt:?}");
let Some(path) = path_map.get(&evt.wd) else {
continue;
};
let path = path.to_path_buf();
if let Some(extra) = evt.name {
evts.push(dbg!(PathEvent {
path: path.join(extra),
kind: None,
}))
//path = dbg!(path.join(extra));
}
evts.push(dbg!(PathEvent { path, kind: None }));
}
}
Err(e) => match e.kind() {
std::io::ErrorKind::WouldBlock => {
//dbg!()
}
_ => {
util::log_err(&e);
//dbg!(&e);
//return Err(e.into());
}
},
}
if !evts.is_empty() {
tx_events.send(evts).await?;
}
//dbg!();
std::thread::yield_now();
}
});
task.detach();
(
Self {
inotify: InotifyWatcher { tx: tx_paths },
//task,
poller: PollWatcher {},
},
Box::pin(rx_events),
)
}
}
impl Watcher for LinuxWatcher {
fn add(&self, path: &std::path::Path) -> Result<()> {
block_on(async {
self.inotify
.tx
.send(WatcherStateUpdate::NewPath(path.into()))
.await
})?;
Ok(())
}
fn remove(&self, path: &std::path::Path) -> Result<()> {
block_on(async {
self.inotify
.tx
.send(WatcherStateUpdate::RemPath(path.into()))
.await
})?;
Ok(())
}
}
// For some reason, tests are currently broken. Don't reenable until this is figured
// out.
// #[cfg(test)]
// mod tests {
// use std::{
// path::PathBuf,
// time::{Duration, Instant},
// };
// use collections::HashSet;
// use gpui::TestAppContext;
// use smol::stream::StreamExt;
// use tempfile::TempDir;
// use crate::{Fs, RealFs};
// #[gpui::test]
// async fn randomized_file_watcher_tests(cx: &mut TestAppContext) {
// let dir = TempDir::new().unwrap();
// let fs = RealFs::new(None, cx.executor());
// let (mut events, watcher) = fs.watch(dir.path(), Duration::from_millis(100)).await;
// dbg!("gonna create dir");
// std::fs::create_dir(dir.path().join("test")).unwrap();
// dbg!("created dir");
// let expected_paths: HashSet<PathBuf> = [dir.path().to_owned()].into_iter().collect();
// let mut actual_paths = HashSet::default();
// let start = Instant::now();
// while start.elapsed() < Duration::from_millis(1000) {
// dbg!();
// //std::thread::sleep(Duration::from_secs(1));
// let events = events.next().await;
// dbg!();
// for event in events.into_iter().flatten() {
// actual_paths.insert(dbg!(event.path));
// }
// dbg!();
// //std::fs::write(dir.path().join("filename"), [42]).unwrap();
// }
// assert_eq!(expected_paths, actual_paths);
// drop(watcher)
// }
// }

View File

@@ -703,7 +703,7 @@ async fn test_write_file(cx: &mut TestAppContext) {
.await
.unwrap();
#[cfg(not(target_os = "macos"))]
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
fs::fs_watcher::global(|_| {}).unwrap();
cx.read(|cx| worktree.read(cx).as_local().unwrap().scan_complete())

View File

@@ -379,7 +379,7 @@ pub fn initialize_workspace(
})
.detach();
#[cfg(not(target_os = "macos"))]
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
initialize_file_watcher(window, cx);
if let Some(specs) = window.gpu_specs() {
@@ -525,7 +525,8 @@ fn unstable_version_notification(cx: &mut App) {
});
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
// todo!()
#[cfg(any(target_os = "freebsd"))]
fn initialize_file_watcher(window: &mut Window, cx: &mut Context<Workspace>) {
if let Err(e) = fs::fs_watcher::global(|_| {}) {
let message = format!(