1use std::collections::{HashMap, HashSet};
8use std::sync::Mutex;
9
10use par_term_emu_core_rust::observer::TerminalObserver;
11use par_term_emu_core_rust::terminal::TerminalEvent;
12
13use super::protocol::{ScriptEvent, ScriptEventData};
14
15pub struct ScriptEventForwarder {
21 subscription_filter: Option<HashSet<String>>,
24 event_buffer: Mutex<Vec<ScriptEvent>>,
27}
28
29impl ScriptEventForwarder {
30 pub fn new(subscriptions: Option<HashSet<String>>) -> Self {
36 Self {
37 subscription_filter: subscriptions,
38 event_buffer: Mutex::new(Vec::new()),
39 }
40 }
41
42 pub fn drain_events(&self) -> Vec<ScriptEvent> {
44 let mut buf = self.event_buffer.lock().expect("event_buffer poisoned");
45 std::mem::take(&mut *buf)
46 }
47
48 fn event_kind_name(event: &TerminalEvent) -> String {
50 match event {
51 TerminalEvent::BellRang(_) => "bell_rang".to_string(),
52 TerminalEvent::TitleChanged(_) => "title_changed".to_string(),
53 TerminalEvent::SizeChanged(_, _) => "size_changed".to_string(),
54 TerminalEvent::ModeChanged(_, _) => "mode_changed".to_string(),
55 TerminalEvent::GraphicsAdded(_) => "graphics_added".to_string(),
56 TerminalEvent::HyperlinkAdded { .. } => "hyperlink_added".to_string(),
57 TerminalEvent::DirtyRegion(_, _) => "dirty_region".to_string(),
58 TerminalEvent::CwdChanged(_) => "cwd_changed".to_string(),
59 TerminalEvent::TriggerMatched(_) => "trigger_matched".to_string(),
60 TerminalEvent::UserVarChanged { .. } => "user_var_changed".to_string(),
61 TerminalEvent::ProgressBarChanged { .. } => "progress_bar_changed".to_string(),
62 TerminalEvent::BadgeChanged(_) => "badge_changed".to_string(),
63 TerminalEvent::ShellIntegrationEvent { .. } => "command_complete".to_string(),
64 TerminalEvent::ZoneOpened { .. } => "zone_opened".to_string(),
65 TerminalEvent::ZoneClosed { .. } => "zone_closed".to_string(),
66 TerminalEvent::ZoneScrolledOut { .. } => "zone_scrolled_out".to_string(),
67 TerminalEvent::EnvironmentChanged { .. } => "environment_changed".to_string(),
68 TerminalEvent::RemoteHostTransition { .. } => "remote_host_transition".to_string(),
69 TerminalEvent::SubShellDetected { .. } => "sub_shell_detected".to_string(),
70 TerminalEvent::FileTransferStarted { .. } => "file_transfer_started".to_string(),
71 TerminalEvent::FileTransferProgress { .. } => "file_transfer_progress".to_string(),
72 TerminalEvent::FileTransferCompleted { .. } => "file_transfer_completed".to_string(),
73 TerminalEvent::FileTransferFailed { .. } => "file_transfer_failed".to_string(),
74 TerminalEvent::UploadRequested { .. } => "upload_requested".to_string(),
75 TerminalEvent::ScreenCleared { .. } => "screen_cleared".to_string(),
76 }
77 }
78
79 fn convert_event(event: &TerminalEvent) -> ScriptEvent {
81 let kind = Self::event_kind_name(event);
82
83 let data = match event {
84 TerminalEvent::BellRang(_) => ScriptEventData::Empty {},
85
86 TerminalEvent::TitleChanged(title) => ScriptEventData::TitleChanged {
87 title: title.clone(),
88 },
89
90 TerminalEvent::SizeChanged(cols, rows) => ScriptEventData::SizeChanged {
91 cols: *cols,
92 rows: *rows,
93 },
94
95 TerminalEvent::CwdChanged(cwd_change) => ScriptEventData::CwdChanged {
96 cwd: cwd_change.new_cwd.clone(),
97 },
98
99 TerminalEvent::UserVarChanged {
100 name,
101 value,
102 old_value,
103 } => ScriptEventData::VariableChanged {
104 name: name.clone(),
105 value: value.clone(),
106 old_value: old_value.clone(),
107 },
108
109 TerminalEvent::EnvironmentChanged {
110 key,
111 value,
112 old_value,
113 } => ScriptEventData::EnvironmentChanged {
114 key: key.clone(),
115 value: value.clone(),
116 old_value: old_value.clone(),
117 },
118
119 TerminalEvent::BadgeChanged(text) => {
120 ScriptEventData::BadgeChanged { text: text.clone() }
121 }
122
123 TerminalEvent::ShellIntegrationEvent {
124 command, exit_code, ..
125 } => ScriptEventData::CommandComplete {
126 command: command.clone().unwrap_or_default(),
127 exit_code: *exit_code,
128 },
129
130 TerminalEvent::TriggerMatched(trigger_match) => ScriptEventData::TriggerMatched {
131 pattern: format!("trigger:{}", trigger_match.trigger_id),
132 matched_text: trigger_match.text.clone(),
133 line: trigger_match.row,
134 },
135
136 TerminalEvent::ZoneOpened {
137 zone_id, zone_type, ..
138 } => ScriptEventData::ZoneEvent {
139 zone_id: *zone_id as u64,
140 zone_type: zone_type.to_string(),
141 event: "opened".to_string(),
142 },
143
144 TerminalEvent::ZoneClosed {
145 zone_id, zone_type, ..
146 } => ScriptEventData::ZoneEvent {
147 zone_id: *zone_id as u64,
148 zone_type: zone_type.to_string(),
149 event: "closed".to_string(),
150 },
151
152 TerminalEvent::ZoneScrolledOut {
153 zone_id, zone_type, ..
154 } => ScriptEventData::ZoneEvent {
155 zone_id: *zone_id as u64,
156 zone_type: zone_type.to_string(),
157 event: "scrolled_out".to_string(),
158 },
159
160 other => {
162 let mut fields = HashMap::new();
163 fields.insert(
164 "debug".to_string(),
165 serde_json::Value::String(format!("{:?}", other)),
166 );
167 ScriptEventData::Generic { fields }
168 }
169 };
170
171 ScriptEvent { kind, data }
172 }
173}
174
175impl TerminalObserver for ScriptEventForwarder {
186 fn on_event(&self, event: &TerminalEvent) {
187 if let Some(ref filter) = self.subscription_filter {
189 let kind = Self::event_kind_name(event);
190 if !filter.contains(&kind) {
191 return;
192 }
193 }
194
195 let script_event = Self::convert_event(event);
196 let mut buf = self.event_buffer.lock().expect("event_buffer poisoned");
197 buf.push(script_event);
198 }
199
200 }
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn test_event_kind_name_bell() {
211 let event =
212 TerminalEvent::BellRang(par_term_emu_core_rust::terminal::BellEvent::VisualBell);
213 assert_eq!(ScriptEventForwarder::event_kind_name(&event), "bell_rang");
214 }
215
216 #[test]
217 fn test_event_kind_name_title() {
218 let event = TerminalEvent::TitleChanged("hello".to_string());
219 assert_eq!(
220 ScriptEventForwarder::event_kind_name(&event),
221 "title_changed"
222 );
223 }
224
225 #[test]
226 fn test_convert_bell_event() {
227 let event =
228 TerminalEvent::BellRang(par_term_emu_core_rust::terminal::BellEvent::VisualBell);
229 let script_event = ScriptEventForwarder::convert_event(&event);
230 assert_eq!(script_event.kind, "bell_rang");
231 assert_eq!(script_event.data, ScriptEventData::Empty {});
232 }
233
234 #[test]
235 fn test_convert_title_event() {
236 let event = TerminalEvent::TitleChanged("My Title".to_string());
237 let script_event = ScriptEventForwarder::convert_event(&event);
238 assert_eq!(script_event.kind, "title_changed");
239 assert_eq!(
240 script_event.data,
241 ScriptEventData::TitleChanged {
242 title: "My Title".to_string(),
243 }
244 );
245 }
246
247 #[test]
248 fn test_convert_size_event() {
249 let event = TerminalEvent::SizeChanged(120, 40);
250 let script_event = ScriptEventForwarder::convert_event(&event);
251 assert_eq!(script_event.kind, "size_changed");
252 assert_eq!(
253 script_event.data,
254 ScriptEventData::SizeChanged {
255 cols: 120,
256 rows: 40,
257 }
258 );
259 }
260
261 #[test]
262 fn test_forwarder_no_filter_captures_all() {
263 let fwd = ScriptEventForwarder::new(None);
264 let bell = TerminalEvent::BellRang(par_term_emu_core_rust::terminal::BellEvent::VisualBell);
265 let title = TerminalEvent::TitleChanged("t".to_string());
266
267 fwd.on_event(&bell);
268 fwd.on_event(&title);
269
270 let events = fwd.drain_events();
271 assert_eq!(events.len(), 2);
272 assert_eq!(events[0].kind, "bell_rang");
273 assert_eq!(events[1].kind, "title_changed");
274 }
275
276 #[test]
277 fn test_forwarder_filters_by_subscription() {
278 let filter = HashSet::from(["bell_rang".to_string()]);
279 let fwd = ScriptEventForwarder::new(Some(filter));
280
281 let bell = TerminalEvent::BellRang(par_term_emu_core_rust::terminal::BellEvent::VisualBell);
282 let title = TerminalEvent::TitleChanged("t".to_string());
283
284 fwd.on_event(&bell);
285 fwd.on_event(&title);
286
287 let events = fwd.drain_events();
288 assert_eq!(events.len(), 1);
289 assert_eq!(events[0].kind, "bell_rang");
290 }
291
292 #[test]
293 fn test_drain_clears_buffer() {
294 let fwd = ScriptEventForwarder::new(None);
295 let bell = TerminalEvent::BellRang(par_term_emu_core_rust::terminal::BellEvent::VisualBell);
296
297 fwd.on_event(&bell);
298 let events = fwd.drain_events();
299 assert_eq!(events.len(), 1);
300
301 let events2 = fwd.drain_events();
303 assert!(events2.is_empty());
304 }
305}