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
12thread_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#[derive(Debug, Clone)]
24pub struct ThreadLocalContext {
25 pub target_name: String,
26 pub manifest_path: String,
27}
28
29impl ThreadLocalContext {
30 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 pub fn get_context() -> ThreadLocalContext {
46 THREAD_CONTEXT.with(|ctx| ctx.borrow().clone())
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
52pub enum CargoDiagnosticLevel {
53 Error,
54 Warning,
55 Help,
56 Note,
57}
58
59#[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#[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<
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#[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 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 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 (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 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}