Skip to main content

cersei_tools/
file_watcher.rs

1//! File watching: detect changes in the project directory.
2
3#[cfg(feature = "file-watch")]
4use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher};
5use parking_lot::Mutex;
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8
9/// Tracks recently changed files.
10#[derive(Debug, Clone, Default)]
11pub struct FileChangeTracker {
12    changed_files: Vec<PathBuf>,
13    max_tracked: usize,
14}
15
16impl FileChangeTracker {
17    pub fn new(max: usize) -> Self {
18        Self {
19            changed_files: Vec::new(),
20            max_tracked: max,
21        }
22    }
23
24    pub fn record_change(&mut self, path: PathBuf) {
25        if !self.changed_files.contains(&path) {
26            self.changed_files.push(path);
27            if self.changed_files.len() > self.max_tracked {
28                self.changed_files.remove(0);
29            }
30        }
31    }
32
33    pub fn drain(&mut self) -> Vec<PathBuf> {
34        std::mem::take(&mut self.changed_files)
35    }
36
37    pub fn recent(&self) -> &[PathBuf] {
38        &self.changed_files
39    }
40}
41
42/// Start watching a directory for file changes.
43#[cfg(feature = "file-watch")]
44pub fn watch_directory(
45    root: &Path,
46    tracker: Arc<Mutex<FileChangeTracker>>,
47) -> Option<RecommendedWatcher> {
48    let root = root.to_path_buf();
49    let mut watcher = notify::recommended_watcher(move |res: Result<Event, _>| {
50        if let Ok(event) = res {
51            for path in event.paths {
52                // Skip hidden files, target dirs, node_modules
53                let path_str = path.display().to_string();
54                if path_str.contains("/.git/")
55                    || path_str.contains("/target/")
56                    || path_str.contains("/node_modules/")
57                    || path_str.contains("/__pycache__/")
58                {
59                    continue;
60                }
61                tracker.lock().record_change(path);
62            }
63        }
64    })
65    .ok()?;
66
67    watcher.watch(&root, RecursiveMode::Recursive).ok()?;
68    Some(watcher)
69}
70
71#[cfg(not(feature = "file-watch"))]
72pub fn watch_directory(_root: &Path, _tracker: Arc<Mutex<FileChangeTracker>>) -> Option<()> {
73    None
74}