use std::{ path::PathBuf, sync::{Arc, Mutex}, }; use anyhow::Result; use log::warn; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; use rustc_hash::FxHashSet; use crate::{ Document, DocumentLanguage, OpenHandler, Uri, Workspace, WorkspaceSource, WorkspaceSubset, }; pub struct DocumentWatcher { workspace: Arc, watcher: Mutex, watched_paths: Mutex>, } impl DocumentWatcher where W: Workspace + Send + Sync + 'static, { pub fn new(workspace: Arc) -> Result { let watcher = Self::create_watcher(Arc::clone(&workspace))?; Ok(Self { workspace, watcher: Mutex::new(watcher), watched_paths: Mutex::default(), }) } fn create_watcher(workspace: Arc) -> Result { let watcher = notify::recommended_watcher(move |event: notify::Result| { if let Ok(event) = event { if event.kind.is_modify() { for path in event.paths { let _ = workspace.reload(path); } } } })?; Ok(watcher) } } impl Workspace for DocumentWatcher { fn open( &self, uri: Arc, text: String, language: DocumentLanguage, source: WorkspaceSource, ) -> Arc { let document = self.workspace.open(uri, text, language, source); if document.uri.scheme() == "file" { if let Ok(mut path) = document.uri.to_file_path() { path.pop(); if let Err(why) = self.watch(path, RecursiveMode::NonRecursive) { warn!( "Failed to watch folder of document \"{}\": {}", document.uri, why ); } } } document } fn register_open_handler(&self, handler: OpenHandler) { self.workspace.register_open_handler(handler); } fn documents(&self) -> Vec> { self.workspace.documents() } fn has(&self, uri: &Uri) -> bool { self.workspace.has(uri) } fn get(&self, uri: &Uri) -> Option> { self.workspace.get(uri) } fn close(&self, uri: &Uri) { self.workspace.close(uri) } fn is_open(&self, uri: &Uri) -> bool { self.workspace.is_open(uri) } fn subset(&self, uri: Arc) -> Option { self.workspace.subset(uri) } fn watch(&self, path: PathBuf, mode: RecursiveMode) -> Result<()> { let mut watched_paths = self.watched_paths.lock().unwrap(); if !watched_paths.contains(&path) { self.watcher.lock().unwrap().watch(&path, mode)?; watched_paths.insert(path); } Ok(()) } }