1use std::collections::HashMap;
10use std::path::PathBuf;
11use std::sync::Mutex;
12use std::time::Instant;
13
14static LAST_CHANGE: Mutex<Option<HashMap<PathBuf, Instant>>> = Mutex::new(None);
16
17fn with_state<R>(f: impl FnOnce(&mut HashMap<PathBuf, Instant>) -> R) -> R {
18 let mut guard = LAST_CHANGE.lock().unwrap();
19 let map = guard.get_or_insert_with(HashMap::new);
20 f(map)
21}
22
23pub fn document_changed(file: &str) {
27 let path = PathBuf::from(file);
28 with_state(|map| {
29 map.insert(path, Instant::now());
30 });
31}
32
33pub fn is_idle(file: &str, debounce_ms: u64) -> bool {
37 let path = PathBuf::from(file);
38 with_state(|map| {
39 match map.get(&path) {
40 None => true, Some(last) => last.elapsed().as_millis() >= debounce_ms as u128,
42 }
43 })
44}
45
46pub fn await_idle(file: &str, debounce_ms: u64, timeout_ms: u64) -> bool {
52 let start = Instant::now();
53 let timeout = std::time::Duration::from_millis(timeout_ms);
54 let poll_interval = std::time::Duration::from_millis(100);
55
56 loop {
57 if is_idle(file, debounce_ms) {
58 return true;
59 }
60 if start.elapsed() >= timeout {
61 return false;
62 }
63 std::thread::sleep(poll_interval);
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn idle_when_no_changes() {
73 assert!(is_idle("/tmp/test-no-changes.md", 1500));
74 }
75
76 #[test]
77 fn not_idle_after_change() {
78 document_changed("/tmp/test-just-changed.md");
79 assert!(!is_idle("/tmp/test-just-changed.md", 1500));
80 }
81
82 #[test]
83 fn idle_after_debounce_period() {
84 document_changed("/tmp/test-debounce.md");
85 std::thread::sleep(std::time::Duration::from_millis(50));
87 assert!(is_idle("/tmp/test-debounce.md", 10));
88 }
89
90 #[test]
91 fn await_idle_returns_immediately_when_idle() {
92 let start = Instant::now();
93 assert!(await_idle("/tmp/test-await-idle.md", 100, 5000));
94 assert!(start.elapsed().as_millis() < 200);
95 }
96
97 #[test]
98 fn await_idle_waits_for_settle() {
99 document_changed("/tmp/test-await-settle.md");
100 let start = Instant::now();
101 assert!(await_idle("/tmp/test-await-settle.md", 200, 5000));
102 assert!(start.elapsed().as_millis() >= 200);
103 }
104}