1use notify::EventKind;
2use parking_lot::Mutex;
3use std::sync::{Arc, OnceLock};
4use util::ResultExt;
5
6use crate::{PathEvent, PathEventKind, Watcher};
7
8pub struct LinuxWatcher {
9 tx: smol::channel::Sender<()>,
10 pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
11}
12
13impl LinuxWatcher {
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 LinuxWatcher {
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.inotify
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.inotify.lock().unwatch(path))??)
83 }
84}
85
86pub struct GlobalWatcher {
87 // two mutexes because calling inotify.add triggers an inotify.event, which needs watchers.
88 pub(super) inotify: Mutex<notify::INotifyWatcher>,
89 pub(super) watchers: Mutex<Vec<Box<dyn Fn(¬ify::Event) + Send + Sync>>>,
90}
91
92impl GlobalWatcher {
93 pub(super) fn add(&self, cb: impl Fn(¬ify::Event) + Send + Sync + 'static) {
94 self.watchers.lock().push(Box::new(cb))
95 }
96}
97
98static INOTIFY_INSTANCE: OnceLock<anyhow::Result<GlobalWatcher, notify::Error>> = OnceLock::new();
99
100fn handle_event(event: Result<notify::Event, notify::Error>) {
101 let Some(event) = event.log_err() else { return };
102 global::<()>(move |watcher| {
103 for f in watcher.watchers.lock().iter() {
104 f(&event)
105 }
106 })
107 .log_err();
108}
109
110pub fn global<T>(f: impl FnOnce(&GlobalWatcher) -> T) -> anyhow::Result<T> {
111 let result = INOTIFY_INSTANCE.get_or_init(|| {
112 notify::recommended_watcher(handle_event).map(|file_watcher| GlobalWatcher {
113 inotify: Mutex::new(file_watcher),
114 watchers: Default::default(),
115 })
116 });
117 match result {
118 Ok(g) => Ok(f(g)),
119 Err(e) => Err(anyhow::anyhow!("{}", e)),
120 }
121}