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