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 }
76 }
77
78 fn convert_event(event: &TerminalEvent) -> ScriptEvent {
80 let kind = Self::event_kind_name(event);
81
82 let data = match event {
83 TerminalEvent::BellRang(_) => ScriptEventData::Empty {},
84
85 TerminalEvent::TitleChanged(title) => ScriptEventData::TitleChanged {
86 title: title.clone(),
87 },
88
89 TerminalEvent::SizeChanged(cols, rows) => ScriptEventData::SizeChanged {
90 cols: *cols,
91 rows: *rows,
92 },
93
94 TerminalEvent::CwdChanged(cwd_change) => ScriptEventData::CwdChanged {
95 cwd: cwd_change.new_cwd.clone(),
96 },
97
98 TerminalEvent::UserVarChanged {
99 name,
100 value,
101 old_value,
102 } => ScriptEventData::VariableChanged {
103 name: name.clone(),
104 value: value.clone(),
105 old_value: old_value.clone(),
106 },
107
108 TerminalEvent::EnvironmentChanged {
109 key,
110 value,
111 old_value,
112 } => ScriptEventData::EnvironmentChanged {
113 key: key.clone(),
114 value: value.clone(),
115 old_value: old_value.clone(),
116 },
117
118 TerminalEvent::BadgeChanged(text) => {
119 ScriptEventData::BadgeChanged { text: text.clone() }
120 }
121
122 TerminalEvent::ShellIntegrationEvent {
123 command, exit_code, ..
124 } => ScriptEventData::CommandComplete {
125 command: command.clone().unwrap_or_default(),
126 exit_code: *exit_code,
127 },
128
129 TerminalEvent::TriggerMatched(trigger_match) => ScriptEventData::TriggerMatched {
130 pattern: format!("trigger:{}", trigger_match.trigger_id),
131 matched_text: trigger_match.text.clone(),
132 line: trigger_match.row,
133 },
134
135 TerminalEvent::ZoneOpened {
136 zone_id, zone_type, ..
137 } => ScriptEventData::ZoneEvent {
138 zone_id: *zone_id as u64,
139 zone_type: zone_type.to_string(),
140 event: "opened".to_string(),
141 },
142
143 TerminalEvent::ZoneClosed {
144 zone_id, zone_type, ..
145 } => ScriptEventData::ZoneEvent {
146 zone_id: *zone_id as u64,
147 zone_type: zone_type.to_string(),
148 event: "closed".to_string(),
149 },
150
151 TerminalEvent::ZoneScrolledOut {
152 zone_id, zone_type, ..
153 } => ScriptEventData::ZoneEvent {
154 zone_id: *zone_id as u64,
155 zone_type: zone_type.to_string(),
156 event: "scrolled_out".to_string(),
157 },
158
159 other => {
161 let mut fields = HashMap::new();
162 fields.insert(
163 "debug".to_string(),
164 serde_json::Value::String(format!("{:?}", other)),
165 );
166 ScriptEventData::Generic { fields }
167 }
168 };
169
170 ScriptEvent { kind, data }
171 }
172}
173
174impl TerminalObserver for ScriptEventForwarder {
185 fn on_event(&self, event: &TerminalEvent) {
186 if let Some(ref filter) = self.subscription_filter {
188 let kind = Self::event_kind_name(event);
189 if !filter.contains(&kind) {
190 return;
191 }
192 }
193
194 let script_event = Self::convert_event(event);
195 let mut buf = self.event_buffer.lock().expect("event_buffer poisoned");
196 buf.push(script_event);
197 }
198
199 }
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_event_kind_name_bell() {
210 let event =
211 TerminalEvent::BellRang(par_term_emu_core_rust::terminal::BellEvent::VisualBell);
212 assert_eq!(ScriptEventForwarder::event_kind_name(&event), "bell_rang");
213 }
214
215 #[test]
216 fn test_event_kind_name_title() {
217 let event = TerminalEvent::TitleChanged("hello".to_string());
218 assert_eq!(
219 ScriptEventForwarder::event_kind_name(&event),
220 "title_changed"
221 );
222 }
223
224 #[test]
225 fn test_convert_bell_event() {
226 let event =
227 TerminalEvent::BellRang(par_term_emu_core_rust::terminal::BellEvent::VisualBell);
228 let script_event = ScriptEventForwarder::convert_event(&event);
229 assert_eq!(script_event.kind, "bell_rang");
230 assert_eq!(script_event.data, ScriptEventData::Empty {});
231 }
232
233 #[test]
234 fn test_convert_title_event() {
235 let event = TerminalEvent::TitleChanged("My Title".to_string());
236 let script_event = ScriptEventForwarder::convert_event(&event);
237 assert_eq!(script_event.kind, "title_changed");
238 assert_eq!(
239 script_event.data,
240 ScriptEventData::TitleChanged {
241 title: "My Title".to_string(),
242 }
243 );
244 }
245
246 #[test]
247 fn test_convert_size_event() {
248 let event = TerminalEvent::SizeChanged(120, 40);
249 let script_event = ScriptEventForwarder::convert_event(&event);
250 assert_eq!(script_event.kind, "size_changed");
251 assert_eq!(
252 script_event.data,
253 ScriptEventData::SizeChanged {
254 cols: 120,
255 rows: 40,
256 }
257 );
258 }
259
260 #[test]
261 fn test_forwarder_no_filter_captures_all() {
262 let fwd = ScriptEventForwarder::new(None);
263 let bell = TerminalEvent::BellRang(par_term_emu_core_rust::terminal::BellEvent::VisualBell);
264 let title = TerminalEvent::TitleChanged("t".to_string());
265
266 fwd.on_event(&bell);
267 fwd.on_event(&title);
268
269 let events = fwd.drain_events();
270 assert_eq!(events.len(), 2);
271 assert_eq!(events[0].kind, "bell_rang");
272 assert_eq!(events[1].kind, "title_changed");
273 }
274
275 #[test]
276 fn test_forwarder_filters_by_subscription() {
277 let filter = HashSet::from(["bell_rang".to_string()]);
278 let fwd = ScriptEventForwarder::new(Some(filter));
279
280 let bell = TerminalEvent::BellRang(par_term_emu_core_rust::terminal::BellEvent::VisualBell);
281 let title = TerminalEvent::TitleChanged("t".to_string());
282
283 fwd.on_event(&bell);
284 fwd.on_event(&title);
285
286 let events = fwd.drain_events();
287 assert_eq!(events.len(), 1);
288 assert_eq!(events[0].kind, "bell_rang");
289 }
290
291 #[test]
292 fn test_drain_clears_buffer() {
293 let fwd = ScriptEventForwarder::new(None);
294 let bell = TerminalEvent::BellRang(par_term_emu_core_rust::terminal::BellEvent::VisualBell);
295
296 fwd.on_event(&bell);
297 let events = fwd.drain_events();
298 assert_eq!(events.len(), 1);
299
300 let events2 = fwd.drain_events();
302 assert!(events2.is_empty());
303 }
304}