1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// ---------------- [ File: src/watch_and_reload.rs ]
crate::ix!();
#[async_trait]
impl<P,H:CrateHandleInterface<P>> WatchAndReload for Workspace<P,H>
where for<'async_trait> P: From<PathBuf> + AsRef<Path> + Send + Sync + 'async_trait
{
type Error = WorkspaceError;
async fn watch_and_reload(
&self,
tx: Option<mpsc::Sender<Result<(), WorkspaceError>>>,
runner: Arc<dyn CommandRunner + Send + Sync + 'static>,
cancel_token: CancellationToken,
) -> Result<(), WorkspaceError> {
let workspace_path = self.as_ref().to_path_buf();
// Channel for receiving file change events
let (notify_tx, notify_rx) = async_channel::unbounded();
// Create a `notify` file watcher
let notify_tx_clone = notify_tx.clone();
let mut watcher = RecommendedWatcher::new(
move |res| {
// Send the event over the async_channel
let _ = notify_tx_clone.try_send(res);
},
notify::Config::default(),
)
.map_err(|e| WatchError::NotifyError(e.into()))?;
// Watch the workspace directory recursively
watcher
.watch(&workspace_path, RecursiveMode::Recursive)
.map_err(|e| WatchError::NotifyError(e.into()))?;
// Keep the watcher alive
let _watcher = watcher;
// Process events from the async_channel
loop {
tokio::select! {
res = notify_rx.recv() => {
match res {
Ok(res) => match res {
Ok(event) => {
for path in event.paths.iter() {
if self.is_relevant_change(&path) {
info!("Detected change in file: {:?}", path);
// Trigger rebuild or tests on file change
let rebuild_result = self.rebuild_or_test(runner.as_ref()).await;
if let Some(ref sender) = tx {
let _ = sender.send(rebuild_result).await;
}
}
}
}
Err(e) => {
error!("File watch error: {:?}", e);
let e: Arc<notify::Error> = Arc::new(e);
if let Some(ref sender) = tx {
let _ = sender.send(Err(WorkspaceError::from(e.clone()))).await;
}
return Err(WorkspaceError::from(e));
}
},
Err(_) => {
// The channel has been closed
break;
}
}
},
_ = cancel_token.cancelled() => {
// Received cancellation signal
break;
}
}
}
Ok(())
}
/// Determines if a file change is relevant (e.g., in `src/` or `Cargo.toml` files).
fn is_relevant_change(&self, path: &Path) -> bool {
// Check if the path ends with 'Cargo.toml'
if path.file_name() == Some(std::ffi::OsStr::new("Cargo.toml")) {
return true;
}
// Check if the path is within any crate's 'src/' directory
for crate_handle in self.crates() {
let crate_src_path = crate_handle.as_ref().join("src");
if path.starts_with(&crate_src_path) {
return true;
}
}
false
}
}