cargo_e/
e_eventdispatcher.rs

1use regex::Regex;
2use std::cell::RefCell;
3use std::fmt;
4use std::sync::atomic::AtomicBool;
5use std::sync::atomic::Ordering;
6use std::sync::Arc;
7use std::sync::Mutex;
8
9use crate::e_cargocommand_ext::CargoStats;
10use crate::e_command_builder::TerminalError;
11
12/// Our internal diagnostic level for cargo.
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
14pub enum CargoDiagnosticLevel {
15    Error,
16    Warning,
17    Help,
18    Note,
19}
20
21// /// A line of source code associated with a diagnostic.
22// #[derive(Debug, Clone)]
23// pub struct CargoDiagnosticSpanLine {
24//     pub text: String,
25//     pub highlight_start: usize,
26//     pub highlight_end: usize,
27// }
28
29// A span (i.e. file location) associated with a diagnostic.
30// #[derive(Debug, Clone)]
31// pub struct CargoDiagnosticSpan {
32//     pub file_name: String,
33//     pub line_start: usize,
34//     pub line_end: usize,
35//     pub column_start: usize,
36//     pub column_end: usize,
37//     pub is_primary: bool,
38//     pub text: Vec<CargoDiagnosticSpanLine>,
39//     pub label: Option<String>,
40//     pub suggested_replacement: Option<String>,
41// }
42
43// /// Our internal diagnostic message.
44// #[derive(Debug, Clone)]
45// pub struct CargoDiagnostic {
46//     pub message: String,
47//     pub code: Option<String>,
48//     pub level: CargoDiagnosticLevel,
49//     pub spans: Vec<CargoDiagnosticSpan>,
50//     pub children: Vec<CargoDiagnostic>,
51// }
52
53/// Our callback type enum.
54#[derive(Debug, Clone)]
55pub enum CallbackType {
56    LevelMessage,
57    Warning,
58    Error,
59    Help,
60    Note,
61    Location,
62    OpenedUrl,
63    Unspecified,
64    Suggestion,
65}
66
67/// The callback response produced by our event dispatcher.
68#[derive(Debug, Clone)]
69pub struct CallbackResponse {
70    pub callback_type: CallbackType,
71    pub message: Option<String>,
72    pub file: Option<String>,
73    pub line: Option<usize>,
74    pub column: Option<usize>,
75    pub suggestion: Option<String>,
76    pub terminal_status: Option<TerminalError>,
77}
78
79#[derive(Clone)]
80pub struct PatternCallback {
81    pub pattern: Regex,
82    // pub callback: Arc<dyn Fn(&str) -> Option<CallbackResponse> + Send + Sync>,
83    pub callback: Arc<
84        dyn Fn(
85                &str,
86                Option<regex::Captures>,
87                Arc<AtomicBool>,
88                Arc<Mutex<CargoStats>>,
89                Option<CallbackResponse>,
90            ) -> Option<CallbackResponse>
91            + Send
92            + Sync,
93    >,
94    pub is_reading_multiline: Arc<AtomicBool>,
95}
96
97impl fmt::Debug for PatternCallback {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        f.debug_struct("PatternCallback")
100            .field("pattern", &self.pattern.as_str())
101            .field("callback", &"Closure")
102            .finish()
103    }
104}
105
106impl PatternCallback {
107    pub fn new(
108        pattern: &str,
109        callback: Box<
110            dyn Fn(
111                    &str,
112                    Option<regex::Captures>,
113                    Arc<AtomicBool>,
114                    Arc<Mutex<CargoStats>>,
115                    Option<CallbackResponse>,
116                ) -> Option<CallbackResponse>
117                + Send
118                + Sync,
119        >,
120    ) -> Self {
121        PatternCallback {
122            pattern: Regex::new(pattern).expect("Invalid regex"),
123            callback: Arc::new(callback),
124            is_reading_multiline: Arc::new(AtomicBool::new(false)),
125        }
126    }
127}
128
129/// A simple event dispatcher for output lines.
130#[derive(Clone, Debug)]
131pub struct EventDispatcher {
132    pub callbacks: Arc<Mutex<Vec<PatternCallback>>>,
133}
134
135impl EventDispatcher {
136    pub fn new() -> Self {
137        EventDispatcher {
138            callbacks: Arc::new(Mutex::new(Vec::new())),
139        }
140    }
141
142    /// Add a new callback with a regex pattern.
143    pub fn add_callback(
144        &mut self,
145        pattern: &str,
146        callback: Box<
147            dyn Fn(
148                    &str,
149                    Option<regex::Captures>,
150                    Arc<AtomicBool>,
151                    Arc<Mutex<CargoStats>>,
152                    Option<CallbackResponse>,
153                ) -> Option<CallbackResponse>
154                + Send
155                + Sync,
156        >,
157    ) {
158        if let Ok(mut callbacks) = self.callbacks.lock() {
159            callbacks.push(PatternCallback::new(pattern, callback));
160        } else {
161            eprintln!("Failed to acquire lock on callbacks in add_callback");
162        }
163    }
164
165    /// Dispatch a line to all callbacks that match, and collect their responses.
166    pub fn dispatch(
167        &self,
168        line: &str,
169        stats: Arc<Mutex<CargoStats>>,
170    ) -> Vec<Option<CallbackResponse>> {
171        let mut responses = Vec::new();
172        // Use thread-local for prior response so each dispatch is independent
173        thread_local! {
174            static PRIOR_RESPONSE: RefCell<Option<CallbackResponse>> = RefCell::new(None);
175        }
176        if let Ok(callbacks) = self.callbacks.lock() {
177            for cb in callbacks.iter() {
178                let is_reading_multiline = Arc::clone(&cb.is_reading_multiline);
179                let prior = PRIOR_RESPONSE.with(|p| p.borrow().clone());
180                let response = if is_reading_multiline.load(Ordering::Relaxed) {
181                    // Multiline mode: always call with prior_response
182                    (cb.callback)(
183                        line,
184                        None,
185                        Arc::clone(&is_reading_multiline),
186                        stats.clone(),
187                        prior,
188                    )
189                } else if let Some(captures) = cb.pattern.captures(line) {
190                    (cb.callback)(
191                        line,
192                        Some(captures),
193                        Arc::clone(&is_reading_multiline),
194                        stats.clone(),
195                        None,
196                    )
197                } else if cb.pattern.is_match(line) {
198                    (cb.callback)(
199                        line,
200                        None,
201                        Arc::clone(&is_reading_multiline),
202                        stats.clone(),
203                        None,
204                    )
205                } else {
206                    None
207                };
208                if is_reading_multiline.load(Ordering::Relaxed) {
209                    PRIOR_RESPONSE.with(|p| *p.borrow_mut() = response.clone());
210                }
211                responses.push(response);
212            }
213        } else {
214            eprintln!("Failed to acquire lock on callbacks in dispatch");
215        }
216        responses
217    }
218
219    /// Process all lines from a BufRead, dispatching to callbacks.
220    pub fn process_stream<R: std::io::BufRead>(
221        &self,
222        reader: R,
223        stats: Arc<Mutex<CargoStats>>,
224    ) -> Vec<CallbackResponse> {
225        let mut responses = Vec::new();
226        for line in reader.lines() {
227            if let Ok(line) = line {
228                let res = self.dispatch(&line, Arc::clone(&stats));
229                for r in res {
230                    if let Some(cb) = r {
231                        responses.push(cb);
232                    }
233                }
234            }
235        }
236        responses
237    }
238}