Skip to main content

fret_runtime/
clipboard_diagnostics.rs

1use std::collections::HashMap;
2
3use fret_core::{AppWindowId, ClipboardToken, FrameId};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct ClipboardReadDiagnostics {
7    pub token: ClipboardToken,
8    pub unavailable: bool,
9    pub message: Option<String>,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct ClipboardWriteDiagnostics {
14    pub unavailable: bool,
15    pub message: Option<String>,
16}
17
18#[derive(Debug, Default)]
19pub struct WindowClipboardDiagnosticsStore {
20    per_window: HashMap<AppWindowId, WindowClipboardDiagnosticsFrame>,
21}
22
23#[derive(Debug, Default)]
24struct WindowClipboardDiagnosticsFrame {
25    frame_id: FrameId,
26    last_read: Option<ClipboardReadDiagnostics>,
27    last_write: Option<ClipboardWriteDiagnostics>,
28}
29
30impl WindowClipboardDiagnosticsStore {
31    pub fn record_read_ok(
32        &mut self,
33        window: AppWindowId,
34        frame_id: FrameId,
35        token: ClipboardToken,
36    ) {
37        let entry = self.per_window.entry(window).or_default();
38        if entry.frame_id != frame_id {
39            entry.frame_id = frame_id;
40            entry.last_read = None;
41            entry.last_write = None;
42        }
43        entry.last_read = Some(ClipboardReadDiagnostics {
44            token,
45            unavailable: false,
46            message: None,
47        });
48    }
49
50    pub fn record_read_unavailable(
51        &mut self,
52        window: AppWindowId,
53        frame_id: FrameId,
54        token: ClipboardToken,
55        message: Option<String>,
56    ) {
57        let entry = self.per_window.entry(window).or_default();
58        if entry.frame_id != frame_id {
59            entry.frame_id = frame_id;
60            entry.last_read = None;
61            entry.last_write = None;
62        }
63        entry.last_read = Some(ClipboardReadDiagnostics {
64            token,
65            unavailable: true,
66            message,
67        });
68    }
69
70    pub fn record_write_ok(&mut self, window: AppWindowId, frame_id: FrameId) {
71        let entry = self.per_window.entry(window).or_default();
72        if entry.frame_id != frame_id {
73            entry.frame_id = frame_id;
74            entry.last_read = None;
75            entry.last_write = None;
76        }
77        entry.last_write = Some(ClipboardWriteDiagnostics {
78            unavailable: false,
79            message: None,
80        });
81    }
82
83    pub fn record_write_unavailable(
84        &mut self,
85        window: AppWindowId,
86        frame_id: FrameId,
87        message: Option<String>,
88    ) {
89        let entry = self.per_window.entry(window).or_default();
90        if entry.frame_id != frame_id {
91            entry.frame_id = frame_id;
92            entry.last_read = None;
93            entry.last_write = None;
94        }
95        entry.last_write = Some(ClipboardWriteDiagnostics {
96            unavailable: true,
97            message,
98        });
99    }
100
101    pub fn last_read_for_window(
102        &self,
103        window: AppWindowId,
104        frame_id: FrameId,
105    ) -> Option<&ClipboardReadDiagnostics> {
106        self.per_window
107            .get(&window)
108            .filter(|entry| entry.frame_id == frame_id)
109            .and_then(|entry| entry.last_read.as_ref())
110    }
111
112    pub fn last_write_for_window(
113        &self,
114        window: AppWindowId,
115        frame_id: FrameId,
116    ) -> Option<&ClipboardWriteDiagnostics> {
117        self.per_window
118            .get(&window)
119            .filter(|entry| entry.frame_id == frame_id)
120            .and_then(|entry| entry.last_write.as_ref())
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn write_diagnostics_reset_per_frame_and_coexist_with_read() {
130        let window = AppWindowId::default();
131        let token = ClipboardToken::default();
132        let frame_1 = FrameId(1);
133        let frame_2 = FrameId(2);
134
135        let mut store = WindowClipboardDiagnosticsStore::default();
136        store.record_write_unavailable(window, frame_1, Some("nope".to_string()));
137
138        let write_1 = store
139            .last_write_for_window(window, frame_1)
140            .expect("last_write frame 1");
141        assert!(write_1.unavailable);
142        assert_eq!(write_1.message.as_deref(), Some("nope"));
143
144        assert!(store.last_write_for_window(window, frame_2).is_none());
145
146        store.record_write_ok(window, frame_2);
147        let write_2 = store
148            .last_write_for_window(window, frame_2)
149            .expect("last_write frame 2");
150        assert!(!write_2.unavailable);
151        assert!(write_2.message.is_none());
152
153        // Recording read diagnostics in the same frame should not clear the write entry.
154        store.record_read_ok(window, frame_2, token);
155        assert!(store.last_read_for_window(window, frame_2).is_some());
156        assert!(store.last_write_for_window(window, frame_2).is_some());
157    }
158}