use std::fmt::Display;
use std::fmt::Formatter;
use std::path::Path;
use std::path::PathBuf;
use notify::Error;
use notify::RecursiveMode;
use notify::Watcher;
use tui_pane::PERF_LOG_TARGET;
use crate::constants::DOT_CARGO_DIR;
use crate::project::AbsolutePath;
#[derive(Clone)]
pub(super) struct RegisteredRoots {
dirs: Vec<AbsolutePath>,
}
impl RegisteredRoots {
pub(super) fn dirs(&self) -> &[AbsolutePath] { &self.dirs }
pub(super) fn add_registered_dir(&mut self, dir: AbsolutePath) { self.dirs.push(dir); }
#[cfg(test)]
pub(super) const fn from_dirs(dirs: Vec<AbsolutePath>) -> Self { Self { dirs } }
pub(super) fn covers(&self, path: &Path) -> bool {
self.dirs.iter().any(|root| path.starts_with(root))
}
}
impl Default for RegisteredRoots {
fn default() -> Self { Self { dirs: Vec::new() } }
}
pub(super) struct WatchRootRegistrationFailure {
pub(super) dir: AbsolutePath,
pub(super) reason: WatchRootRegistrationFailureReason,
}
pub(super) enum WatchRootRegistrationFailureReason {
NotADirectory,
Notify(Error),
}
impl Display for WatchRootRegistrationFailureReason {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotADirectory => f.write_str("path is not a directory"),
Self::Notify(err) => write!(f, "notify watch failed: {err}"),
}
}
}
pub(super) fn register_watch_roots(
watcher: &mut impl Watcher,
watch_dirs: &[AbsolutePath],
) -> (RegisteredRoots, Vec<WatchRootRegistrationFailure>) {
let mut registered = Vec::with_capacity(watch_dirs.len());
let mut failures = Vec::new();
for dir in watch_dirs {
if !dir.is_dir() {
failures.push(WatchRootRegistrationFailure {
dir: dir.clone(),
reason: WatchRootRegistrationFailureReason::NotADirectory,
});
continue;
}
match watcher.watch(dir, RecursiveMode::Recursive) {
Ok(()) => registered.push(dir.clone()),
Err(err) => failures.push(WatchRootRegistrationFailure {
dir: dir.clone(),
reason: WatchRootRegistrationFailureReason::Notify(err),
}),
}
}
(RegisteredRoots { dirs: registered }, failures)
}
fn resolve_cargo_home() -> Option<PathBuf> {
if let Ok(home) = std::env::var("CARGO_HOME")
&& !home.is_empty()
{
return Some(PathBuf::from(home));
}
dirs::home_dir().map(|home| home.join(DOT_CARGO_DIR))
}
pub(super) fn register_cargo_home_watch(
watcher: &mut impl Watcher,
registered_roots: &RegisteredRoots,
) {
let Some(cargo_home) = resolve_cargo_home() else {
return;
};
if !cargo_home.is_dir() {
return;
}
if registered_roots.covers(cargo_home.as_path()) {
return;
}
match watcher.watch(cargo_home.as_path(), RecursiveMode::NonRecursive) {
Ok(()) => tracing::trace!(
target: PERF_LOG_TARGET,
cargo_home = %cargo_home.display(),
"watcher_cargo_home_registered"
),
Err(err) => tracing::error!(
cargo_home = %cargo_home.display(),
error = %err,
"watcher_cargo_home_registration_failed"
),
}
}