fresh/model/
control_event.rs1use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use std::collections::VecDeque;
14use std::sync::{Arc, Mutex};
15
16pub mod events {
20 use serde_json::{json, Value};
21
22 pub struct EventDef {
24 pub name: &'static str,
25 pub description: &'static str,
26 pub data_schema_fn: fn() -> Value,
28 }
29
30 pub const FILE_OPENED: EventDef = EventDef {
33 name: "editor:file_opened",
34 description: "File opened in editor",
35 data_schema_fn: || json!({"path": "string", "buffer_id": "number"}),
36 };
37
38 pub const FILE_SAVED: EventDef = EventDef {
39 name: "editor:file_saved",
40 description: "File saved to disk",
41 data_schema_fn: || json!({"path": "string"}),
42 };
43
44 pub const LSP_STATUS_CHANGED: EventDef = EventDef {
47 name: "lsp:status_changed",
48 description: "LSP server status changed",
49 data_schema_fn: || json!({"language": "string", "old_status": "string", "status": "string"}),
50 };
51
52 pub fn all_events() -> Vec<&'static EventDef> {
54 vec![&FILE_OPENED, &FILE_SAVED, &LSP_STATUS_CHANGED]
55 }
56
57 pub fn schema() -> Value {
59 let mut events = serde_json::Map::new();
60 for event in all_events() {
61 events.insert(
62 event.name.to_string(),
63 json!({
64 "description": event.description,
65 "data": (event.data_schema_fn)()
66 }),
67 );
68 }
69 Value::Object(events)
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
75pub struct ControlEvent {
76 pub name: String,
78 pub data: Value,
80}
81
82impl ControlEvent {
83 pub fn new(name: impl Into<String>, data: Value) -> Self {
85 Self {
86 name: name.into(),
87 data,
88 }
89 }
90
91 pub fn simple(name: impl Into<String>) -> Self {
93 Self {
94 name: name.into(),
95 data: Value::Null,
96 }
97 }
98
99 pub fn matches(&self, pattern: &str) -> bool {
102 if pattern == "*" {
103 return true;
104 }
105
106 if !pattern.contains('*') {
107 return self.name == pattern;
108 }
109
110 let parts: Vec<&str> = pattern.split('*').collect();
112 if parts.len() == 2 {
113 let (prefix, suffix) = (parts[0], parts[1]);
114 self.name.starts_with(prefix) && self.name.ends_with(suffix)
115 } else {
116 self.name == pattern
118 }
119 }
120
121 pub fn data_matches(&self, expected: &Value) -> bool {
123 match (expected, &self.data) {
124 (Value::Null, _) => true, (Value::Object(exp_map), Value::Object(data_map)) => {
126 exp_map.iter().all(|(k, v)| {
128 data_map.get(k).is_some_and(|data_v| {
129 if v.is_null() {
130 true } else {
132 v == data_v
133 }
134 })
135 })
136 }
137 _ => expected == &self.data,
138 }
139 }
140}
141
142#[derive(Clone)]
144pub struct EventBroadcaster {
145 events: Arc<Mutex<VecDeque<ControlEvent>>>,
146 max_history: usize,
147}
148
149impl EventBroadcaster {
150 pub fn new(max_history: usize) -> Self {
151 Self {
152 events: Arc::new(Mutex::new(VecDeque::with_capacity(max_history))),
153 max_history,
154 }
155 }
156
157 pub fn emit(&self, event: ControlEvent) {
159 let mut events = self.events.lock().unwrap();
160 if events.len() >= self.max_history {
161 events.pop_front();
162 }
163 events.push_back(event);
164 }
165
166 pub fn emit_named(&self, name: impl Into<String>, data: Value) {
168 self.emit(ControlEvent::new(name, data));
169 }
170
171 pub fn emit_simple(&self, name: impl Into<String>) {
173 self.emit(ControlEvent::simple(name));
174 }
175
176 pub fn has_match(&self, name_pattern: &str, data_pattern: &Value) -> bool {
178 let events = self.events.lock().unwrap();
179 events
180 .iter()
181 .any(|e| e.matches(name_pattern) && e.data_matches(data_pattern))
182 }
183
184 pub fn take_match(&self, name_pattern: &str, data_pattern: &Value) -> Option<ControlEvent> {
186 let mut events = self.events.lock().unwrap();
187 let pos = events
188 .iter()
189 .position(|e| e.matches(name_pattern) && e.data_matches(data_pattern));
190
191 if let Some(idx) = pos {
192 let event = events.get(idx).cloned();
193 events.drain(..=idx);
194 event
195 } else {
196 None
197 }
198 }
199
200 pub fn drain(&self) -> Vec<ControlEvent> {
202 let mut events = self.events.lock().unwrap();
203 events.drain(..).collect()
204 }
205
206 pub fn peek(&self) -> Vec<ControlEvent> {
208 let events = self.events.lock().unwrap();
209 events.iter().cloned().collect()
210 }
211
212 pub fn clear(&self) {
214 let mut events = self.events.lock().unwrap();
215 events.clear();
216 }
217
218 pub fn len(&self) -> usize {
220 let events = self.events.lock().unwrap();
221 events.len()
222 }
223
224 pub fn is_empty(&self) -> bool {
226 self.len() == 0
227 }
228}
229
230impl Default for EventBroadcaster {
231 fn default() -> Self {
232 Self::new(1000)
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use serde_json::json;
240
241 #[test]
242 fn test_event_matching() {
243 let event = ControlEvent::new(
244 "lsp:status_changed",
245 json!({"language": "rust", "status": "running"}),
246 );
247
248 assert!(event.matches("lsp:status_changed"));
249 assert!(event.matches("lsp:*"));
250 assert!(event.matches("*:status_changed"));
251 assert!(event.matches("*"));
252 assert!(!event.matches("lsp:error"));
253 assert!(!event.matches("editor:*"));
254 }
255
256 #[test]
257 fn test_data_matching() {
258 let event = ControlEvent::new("test", json!({"a": 1, "b": "hello", "c": true}));
259
260 assert!(event.data_matches(&json!({"a": 1})));
262 assert!(event.data_matches(&json!({"b": "hello"})));
263 assert!(event.data_matches(&json!({"a": 1, "b": "hello"})));
264
265 assert!(event.data_matches(&json!({"a": null})));
267 assert!(event.data_matches(&Value::Null));
268
269 assert!(!event.data_matches(&json!({"a": 2})));
271 assert!(!event.data_matches(&json!({"d": 1})));
272 }
273
274 #[test]
275 fn test_broadcaster() {
276 let bc = EventBroadcaster::new(10);
277
278 bc.emit_simple("editor:init");
279 bc.emit_named(
280 "lsp:status_changed",
281 json!({"language": "rust", "status": "starting"}),
282 );
283 bc.emit_named(
284 "lsp:status_changed",
285 json!({"language": "rust", "status": "running"}),
286 );
287
288 assert_eq!(bc.len(), 3);
289
290 assert!(bc.has_match("lsp:status_changed", &json!({"status": "running"})));
292
293 let event = bc.take_match("lsp:status_changed", &json!({"status": "running"}));
295 assert!(event.is_some());
296 assert_eq!(event.unwrap().data["status"], "running");
297
298 assert_eq!(bc.len(), 0);
300 }
301
302 #[test]
303 fn test_max_history() {
304 let bc = EventBroadcaster::new(2);
305
306 bc.emit_simple("a");
307 bc.emit_simple("b");
308 bc.emit_simple("c");
309
310 let events = bc.drain();
311 assert_eq!(events.len(), 2);
312 assert_eq!(events[0].name, "b");
313 assert_eq!(events[1].name, "c");
314 }
315}