1use notify::EventKind;
2use parking_lot::Mutex;
3use std::sync::{Arc, OnceLock};
4use util::ResultExt;
5
6use crate::{PathEvent, PathEventKind, Watcher};
7
8pub struct FsWatcher {
9 tx: smol::channel::Sender<()>,
10 pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
11}
12
13impl FsWatcher {
14 pub fn new(
15 tx: smol::channel::Sender<()>,
16 pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
17 ) -> Self {
18 Self {
19 tx,
20 pending_path_events,
21 }
22 }
23}
24
25impl Watcher for FsWatcher {
26 fn add(&self, path: &std::path::Path) -> gpui::Result<()> {
27 let root_path = path.to_path_buf();
28
29 let tx = self.tx.clone();
30 let pending_paths = self.pending_path_events.clone();
31
32 use notify::Watcher;
33
34 global({
35 |g| {
36 g.add(move |event: ¬ify::Event| {
37 let kind = match event.kind {
38 EventKind::Create(_) => Some(PathEventKind::Created),
39 EventKind::Modify(_) => Some(PathEventKind::Changed),
40 EventKind::Remove(_) => Some(PathEventKind::Removed),
41 _ => None,
42 };
43 let mut path_events = event
44 .paths
45 .iter()
46 .filter_map(|event_path| {
47 event_path.starts_with(&root_path).then(|| PathEvent {
48 path: event_path.clone(),
49 kind,
50 })
51 })
52 .collect::<Vec<_>>();
53
54 if !path_events.is_empty() {
55 path_events.sort();
56 let mut pending_paths = pending_paths.lock();
57 if pending_paths.is_empty() {
58 tx.try_send(()).ok();
59 }
60 util::extend_sorted(
61 &mut *pending_paths,
62 path_events,
63 usize::MAX,
64 |a, b| a.path.cmp(&b.path),
65 );
66 }
67 })
68 }
69 })?;
70
71 global(|g| {
72 g.watcher
73 .lock()
74 .watch(path, notify::RecursiveMode::NonRecursive)
75 })??;
76
77 Ok(())
78 }
79
80 fn remove(&self, path: &std::path::Path) -> gpui::Result<()> {
81 use notify::Watcher;
82 Ok(global(|w| w.watcher.lock().unwatch(path))??)
83 }
84}
85
86pub struct GlobalWatcher {
87 // two mutexes because calling watcher.add triggers an watcher.event, which needs watchers.
88 #[cfg(target_os = "linux")]
89 pub(super) watcher: Mutex<notify::INotifyWatcher>,
90 #[cfg(target_os = "freebsd")]
91 pub(super) watcher: Mutex<notify::KqueueWatcher>,
92 #[cfg(target_os = "windows")]
93 pub(super) watcher: Mutex<notify::ReadDirectoryChangesWatcher>,
94 pub(super) watchers: Mutex<Vec<Box<dyn Fn(¬ify::Event) + Send + Sync>>>,
95}
96
97impl GlobalWatcher {
98 pub(super) fn add(&self, cb: impl Fn(¬ify::Event) + Send + Sync + 'static) {
99 self.watchers.lock().push(Box::new(cb))
100 }
101}
102
103static FS_WATCHER_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> =
104 OnceLock::new();
105
106fn handle_event(event: Result<notify::Event, notify::Error>) {
107 let Some(event) = event.log_err() else { return };
108 global::<()>(move |watcher| {
109 for f in watcher.watchers.lock().iter() {
110 f(&event)
111 }
112 })
113 .log_err();
114}
115
116pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
117 let result = FS_WATCHER_INSTANCE.get_or_init(|| {
118 notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
119 watcher: Mutex::new(file_watcher),
120 watchers: Default::default(),
121 })
122 });
123 match result {
124 Ok(g) => Ok(f(g)),
125 Err(e) => Err(anyhow::anyhow!("{}", e)),
126 }
127}