use regex::Regex;
use std::cell::RefCell;
use std::fmt;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::Mutex;
use crate::e_cargocommand_ext::CargoStats;
use crate::e_command_builder::TerminalError;
thread_local! {
pub static THREAD_CONTEXT: RefCell<ThreadLocalContext> = RefCell::new(ThreadLocalContext {
target_name: String::new(),
manifest_path: String::new(),
});
static PRIOR_RESPONSE: RefCell<Option<CallbackResponse>> = RefCell::new(None);
}
#[derive(Debug, Clone)]
pub struct ThreadLocalContext {
pub target_name: String,
pub manifest_path: String,
}
impl ThreadLocalContext {
pub fn set_context(target_name: &str, manifest_path: &str) {
log::trace!(
"Setting thread-local context: target_name={}, manifest_path={}",
target_name,
manifest_path
);
THREAD_CONTEXT.with(|ctx| {
let mut context = ctx.borrow_mut();
context.target_name = target_name.to_string();
context.manifest_path = manifest_path.to_string();
});
}
pub fn get_context() -> ThreadLocalContext {
THREAD_CONTEXT.with(|ctx| ctx.borrow().clone())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub enum CargoDiagnosticLevel {
Error,
Warning,
Help,
Note,
}
#[derive(Debug, Clone)]
pub enum CallbackType {
LevelMessage,
Warning,
Error,
Help,
Note,
Location,
OpenedUrl,
Unspecified,
Suggestion,
}
#[derive(Debug, Clone)]
pub struct CallbackResponse {
pub callback_type: CallbackType,
pub message: Option<String>,
pub file: Option<String>,
pub line: Option<usize>,
pub column: Option<usize>,
pub suggestion: Option<String>,
pub terminal_status: Option<TerminalError>,
}
#[derive(Clone)]
pub struct PatternCallback {
pub pattern: Regex,
pub callback: Arc<
dyn Fn(
&str,
Option<regex::Captures>,
Arc<AtomicBool>,
Arc<Mutex<CargoStats>>,
Option<CallbackResponse>,
) -> Option<CallbackResponse>
+ Send
+ Sync,
>,
pub is_reading_multiline: Arc<AtomicBool>,
}
impl fmt::Debug for PatternCallback {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PatternCallback")
.field("pattern", &self.pattern.as_str())
.field("callback", &"Closure")
.finish()
}
}
impl PatternCallback {
pub fn new(
pattern: &str,
callback: Box<
dyn Fn(
&str,
Option<regex::Captures>,
Arc<AtomicBool>,
Arc<Mutex<CargoStats>>,
Option<CallbackResponse>,
) -> Option<CallbackResponse>
+ Send
+ Sync,
>,
) -> Self {
PatternCallback {
pattern: Regex::new(pattern).expect("Invalid regex"),
callback: Arc::new(callback),
is_reading_multiline: Arc::new(AtomicBool::new(false)),
}
}
}
#[derive(Clone, Debug)]
pub struct EventDispatcher {
pub callbacks: Arc<Mutex<Vec<PatternCallback>>>,
}
impl EventDispatcher {
pub fn new() -> Self {
EventDispatcher {
callbacks: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn add_callback(
&mut self,
pattern: &str,
callback: Box<
dyn Fn(
&str,
Option<regex::Captures>,
Arc<AtomicBool>,
Arc<Mutex<CargoStats>>,
Option<CallbackResponse>,
) -> Option<CallbackResponse>
+ Send
+ Sync,
>,
) {
if let Ok(mut callbacks) = self.callbacks.lock() {
callbacks.push(PatternCallback::new(pattern, callback));
} else {
eprintln!("Failed to acquire lock on callbacks in add_callback");
}
}
pub fn dispatch(
&self,
line: &str,
stats: Arc<Mutex<CargoStats>>,
) -> Vec<Option<CallbackResponse>> {
let mut responses = Vec::new();
if let Ok(callbacks) = self.callbacks.lock() {
for cb in callbacks.iter() {
let is_reading_multiline = Arc::clone(&cb.is_reading_multiline);
let prior = PRIOR_RESPONSE.with(|p| p.borrow().clone());
let response = if is_reading_multiline.load(Ordering::Relaxed) {
(cb.callback)(
line,
None,
Arc::clone(&is_reading_multiline),
stats.clone(),
prior,
)
} else if let Some(captures) = cb.pattern.captures(line) {
(cb.callback)(
line,
Some(captures),
Arc::clone(&is_reading_multiline),
stats.clone(),
None,
)
} else if cb.pattern.is_match(line) {
(cb.callback)(
line,
None,
Arc::clone(&is_reading_multiline),
stats.clone(),
None,
)
} else {
None
};
if is_reading_multiline.load(Ordering::Relaxed) {
PRIOR_RESPONSE.with(|p| *p.borrow_mut() = response.clone());
}
responses.push(response);
}
} else {
eprintln!("Failed to acquire lock on callbacks in dispatch");
}
responses
}
pub fn process_stream<R: std::io::BufRead>(
&self,
reader: R,
stats: Arc<Mutex<CargoStats>>,
) -> Vec<CallbackResponse> {
let mut responses = Vec::new();
for line in reader.lines() {
if let Ok(line) = line {
let res = self.dispatch(&line, Arc::clone(&stats));
for r in res {
if let Some(cb) = r {
responses.push(cb);
}
}
}
}
responses
}
}