cargo_e/
e_command_builder.rs

1use regex::Regex;
2use std::collections::{HashMap, HashSet};
3use std::env;
4use std::io::Read;
5use std::path::{Path, PathBuf};
6use std::process::Command;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::mpsc::{channel, Sender};
9use std::time::SystemTime;
10use which::which;
11
12use crate::e_cargocommand_ext::CargoProcessResult;
13use crate::e_cargocommand_ext::{CargoCommandExt, CargoDiagnostic, CargoProcessHandle};
14use crate::e_eventdispatcher::{
15    CallbackResponse, CallbackType, CargoDiagnosticLevel, EventDispatcher, ThreadLocalContext,
16};
17use crate::e_runner::GLOBAL_CHILDREN;
18use crate::e_target::{CargoTarget, TargetKind, TargetOrigin};
19use std::sync::{Arc, Mutex};
20
21#[derive(Debug, Clone, PartialEq, Copy)]
22pub enum TerminalError {
23    NotConnected,
24    NoTerminal,
25    NoError,
26}
27
28impl Default for TerminalError {
29    fn default() -> Self {
30        TerminalError::NoError
31    }
32}
33
34/// A builder that constructs a Cargo command for a given target.
35#[derive(Clone, Debug)]
36pub struct CargoCommandBuilder {
37    pub target_name: String,
38    pub manifest_path: PathBuf,
39    pub args: Vec<String>,
40    pub subcommand: String,
41    pub pid: Option<u32>,
42    pub alternate_cmd: Option<String>,
43    pub execution_dir: Option<PathBuf>,
44    pub suppressed_flags: HashSet<String>,
45    pub stdout_dispatcher: Option<Arc<EventDispatcher>>,
46    pub stderr_dispatcher: Option<Arc<EventDispatcher>>,
47    pub progress_dispatcher: Option<Arc<EventDispatcher>>,
48    pub stage_dispatcher: Option<Arc<EventDispatcher>>,
49    pub terminal_error_flag: Arc<Mutex<bool>>,
50    pub sender: Option<Arc<Mutex<Sender<TerminalError>>>>,
51    pub diagnostics: Arc<Mutex<Vec<CargoDiagnostic>>>,
52    pub is_filter: bool,
53}
54
55impl std::fmt::Display for CargoCommandBuilder {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        write!(
58            f,
59            "CargoCommandBuilder {{\n  target_name: {:?},\n  manifest_path: {:?},\n  args: {:?},\n  subcommand: {:?},\n  pid: {:?},\n  alternate_cmd: {:?},\n  execution_dir: {:?},\n  suppressed_flags: {:?},\n  is_filter: {:?}\n}}",
60            self.target_name,
61            self.manifest_path,
62            self.args,
63            self.subcommand,
64            self.pid,
65            self.alternate_cmd,
66            self.execution_dir,
67            self.suppressed_flags,
68            self.is_filter
69        )
70    }
71}
72impl Default for CargoCommandBuilder {
73    fn default() -> Self {
74        Self::new(
75            &String::new(),
76            &PathBuf::from("Cargo.toml"),
77            "run".into(),
78            false,
79        )
80    }
81}
82impl CargoCommandBuilder {
83    /// Creates a new, empty builder.
84    pub fn new(target_name: &str, manifest: &PathBuf, subcommand: &str, is_filter: bool) -> Self {
85        ThreadLocalContext::set_context(target_name, manifest.to_str().unwrap_or_default());
86        let (sender, _receiver) = channel::<TerminalError>();
87        let sender = Arc::new(Mutex::new(sender));
88        let mut builder = CargoCommandBuilder {
89            target_name: target_name.to_owned(),
90            manifest_path: manifest.clone(),
91            args: Vec::new(),
92            subcommand: subcommand.to_string(),
93            pid: None,
94            alternate_cmd: None,
95            execution_dir: None,
96            suppressed_flags: HashSet::new(),
97            stdout_dispatcher: None,
98            stderr_dispatcher: None,
99            progress_dispatcher: None,
100            stage_dispatcher: None,
101            terminal_error_flag: Arc::new(Mutex::new(false)),
102            sender: Some(sender),
103            diagnostics: Arc::new(Mutex::new(Vec::<CargoDiagnostic>::new())),
104            is_filter,
105        };
106        builder.set_default_dispatchers();
107        builder
108    }
109
110    // Switch to passthrough mode when the terminal error is detected
111    fn switch_to_passthrough_mode<F>(self: Arc<Self>, on_spawn: F) -> anyhow::Result<u32>
112    where
113        F: FnOnce(u32, CargoProcessHandle),
114    {
115        let mut command = self.build_command();
116
117        // Now, spawn the cargo process in passthrough mode
118        let cargo_process_handle = command.spawn_cargo_passthrough(Arc::clone(&self));
119        let pid = cargo_process_handle.pid;
120        // Notify observer
121        on_spawn(pid, cargo_process_handle);
122
123        Ok(pid)
124    }
125
126    // Set up the default dispatchers, which includes error detection
127    fn set_default_dispatchers(&mut self) {
128        if !self.is_filter {
129            // If this is a filter, we don't need to set up dispatchers
130            return;
131        }
132        let sender = self.sender.clone().unwrap();
133
134        let mut stdout_dispatcher = EventDispatcher::new();
135        stdout_dispatcher.add_callback(
136            r"listening on",
137            Box::new(|line, _captures, _state, stats, _prior_response| {
138                println!("(STDOUT) Dispatcher caught: {}", line);
139                // Use a regex to capture a URL from the line.
140                if let Ok(url_regex) = Regex::new(r"(http://[^\s]+)") {
141                    if let Some(url_caps) = url_regex.captures(line) {
142                        if let Some(url_match) = url_caps.get(1) {
143                            let url = url_match.as_str();
144                            // Call open::that on the captured URL.
145                            if let Err(e) = open::that_detached(url) {
146                                eprintln!("Failed to open URL: {}. Error: {}", url, e);
147                            } else {
148                                println!("Opened URL: {}", url);
149                            }
150                        }
151                    }
152                } else {
153                    eprintln!("Failed to create URL regex");
154                }
155                let mut stats = stats.lock().unwrap();
156                if stats.build_finished_time.is_none() {
157                    let now = SystemTime::now();
158                    stats.build_finished_time = Some(now);
159                }
160                None
161            }),
162        );
163
164        stdout_dispatcher.add_callback(
165            r"BuildFinished",
166            Box::new(|line, _captures, _state, stats, _prior_response| {
167                println!("******* {}", line);
168                let mut stats = stats.lock().unwrap();
169                if stats.build_finished_time.is_none() {
170                    let now = SystemTime::now();
171                    stats.build_finished_time = Some(now);
172                }
173                None
174            }),
175        );
176        stdout_dispatcher.add_callback(
177            r"server listening at:",
178            Box::new(|line, _captures, state, stats, _prior_response| {
179                // If we're not already in multiline mode, this is the initial match.
180                if !state.load(Ordering::Relaxed) {
181                    println!("Matched 'server listening at:' in: {}", line);
182                    state.store(true, Ordering::Relaxed);
183                    Some(CallbackResponse {
184                        callback_type: CallbackType::Note, // Choose as appropriate
185                        message: Some(format!("Started multiline mode after: {}", line)),
186                        file: None,
187                        line: None,
188                        column: None,
189                        suggestion: None,
190                        terminal_status: None,
191                    })
192                } else {
193                    // We are in multiline mode; process subsequent lines.
194                    println!("Multiline callback received: {}", line);
195                    // Use a regex to capture a URL from the line.
196                    let url_regex = match Regex::new(r"(http://[^\s]+)") {
197                        Ok(regex) => regex,
198                        Err(e) => {
199                            eprintln!("Failed to create URL regex: {}", e);
200                            return None;
201                        }
202                    };
203                    if let Some(url_caps) = url_regex.captures(line) {
204                        let url = url_caps.get(1).unwrap().as_str();
205                        // Call open::that on the captured URL.
206                        match open::that_detached(url) {
207                            Ok(_) => println!("Opened URL: {}", url),
208                            Err(e) => eprintln!("Failed to open URL: {}. Error: {}", url, e),
209                        }
210                        let mut stats = stats.lock().unwrap();
211                        if stats.build_finished_time.is_none() {
212                            let now = SystemTime::now();
213                            stats.build_finished_time = Some(now);
214                        }
215                        // End multiline mode.
216                        state.store(false, Ordering::Relaxed);
217                        Some(CallbackResponse {
218                            callback_type: CallbackType::Note, // Choose as appropriate
219                            message: Some(format!("Captured and opened URL: {}", url)),
220                            file: None,
221                            line: None,
222                            column: None,
223                            suggestion: None,
224                            terminal_status: None,
225                        })
226                    } else {
227                        None
228                    }
229                }
230            }),
231        );
232
233        let mut stderr_dispatcher = EventDispatcher::new();
234
235        let suggestion_mode = Arc::new(AtomicBool::new(false));
236        let suggestion_regex = Regex::new(r"^\s*(\d+)\s*\|\s*(.*)$").unwrap();
237        let warning_location: Arc<Mutex<Option<CallbackResponse>>> = Arc::new(Mutex::new(None));
238        let pending_diag: Arc<Mutex<Option<CargoDiagnostic>>> = Arc::new(Mutex::new(None));
239        let diagnostic_counts: Arc<Mutex<HashMap<CargoDiagnosticLevel, usize>>> =
240            Arc::new(Mutex::new(HashMap::new()));
241
242        let pending_d = Arc::clone(&pending_diag);
243        let counts = Arc::clone(&diagnostic_counts);
244
245        let diagnostics_arc = Arc::clone(&self.diagnostics);
246        // Callback for Rust panic messages (e.g., "thread 'main' panicked at ...")
247        stderr_dispatcher.add_callback(
248            r"^thread '([^']+)' panicked at (.+):(\d+):(\d+):$",
249            Box::new(|line, captures, multiline_flag, stats, prior_response| {
250                multiline_flag.store(false, Ordering::Relaxed);
251
252                if let Some(caps) = captures {
253                    multiline_flag.store(true, Ordering::Relaxed); // the next line is the panic message
254                    let thread = caps.get(1).map(|m| m.as_str()).unwrap_or("unknown");
255                    let message = caps.get(2).map(|m| m.as_str()).unwrap_or("unknown panic");
256                    let file = caps.get(3).map(|m| m.as_str()).unwrap_or("unknown file");
257                    let line_num = caps
258                        .get(4)
259                        .map(|m| m.as_str())
260                        .unwrap_or("0")
261                        .parse()
262                        .unwrap_or(0);
263                    let col_num = caps
264                        .get(5)
265                        .map(|m| m.as_str())
266                        .unwrap_or("0")
267                        .parse()
268                        .unwrap_or(0);
269                    println!("\n\n\n");
270                    println!("{}", line);
271                    // Use a global TTS instance via OnceCell for program lifetime
272
273                    #[cfg(feature = "uses_tts")]
274                    {
275                        let mut say_something = true;
276                        if let Some(cli) = crate::GLOBAL_CLI.get() {
277                            if cli.no_tts {
278                                say_something = false;
279                            }
280                        }
281                        if say_something {
282                            let tts_mutex = crate::GLOBAL_TTS.get_or_init(|| {
283                                std::sync::Mutex::new(
284                                    tts::Tts::default().expect("TTS engine failure"),
285                                )
286                            });
287                            // Extract the filename without extension
288                            let filename = Path::new(message)
289                                .file_stem()
290                                .and_then(|s| s.to_str())
291                                .unwrap_or("unknown file");
292                            let speech =
293                                format!("thread {} panic, {} line {}", thread, filename, line_num);
294                            println!("TTS: {}", speech);
295                            crate::e_runner::wait_for_tts_to_finish(15000);
296                            let mut tts = tts_mutex.lock().expect("Failed to lock TTS mutex");
297                            let _ = tts.speak(&speech, false);
298                            drop(tts);
299                        }
300                    }
301
302                    println!(
303                        "Panic detected: thread='{}', message='{}', file='{}:{}:{}'",
304                        thread, message, file, line_num, col_num
305                    );
306                    println!("\n\n\n");
307                    Some(CallbackResponse {
308                        callback_type: CallbackType::Error,
309                        message: Some(format!(
310                            "thread '{}' panicked at {} ({}:{}:{})",
311                            thread, message, file, line_num, col_num
312                        )),
313                        file: Some(message.to_string()),
314                        line: Some(file.parse::<usize>().unwrap_or(0)),
315                        column: Some(line_num),
316                        suggestion: None,
317                        terminal_status: None,
318                    })
319                } else {
320                    let context = ThreadLocalContext::get_context();
321                    let mut show_window = true;
322                    let mut say_something = true;
323                    if let Some(cli) = crate::GLOBAL_CLI.get() {
324                        if cli.no_window {
325                            show_window = false;
326                        }
327                        if cli.no_tts {
328                            say_something = false;
329                        }
330                    }
331                    if show_window {
332                        show_graphical_panic(
333                            line,
334                            prior_response,
335                            &PathBuf::from(&context.manifest_path),
336                        );
337                    }
338                    #[cfg(feature = "uses_tts")]
339                    {
340                        if say_something {
341                            let tts_mutex = crate::GLOBAL_TTS.get_or_init(|| {
342                                std::sync::Mutex::new(
343                                    tts::Tts::default().expect("TTS engine failure"),
344                                )
345                            });
346
347                            let speech = format!("panic says {}", line);
348                            println!("TTS: {}", speech);
349                            crate::e_runner::wait_for_tts_to_finish(15000);
350                            let mut tts = tts_mutex.lock().expect("Failed to lock TTS mutex");
351                            let _ = tts.speak(&speech, true);
352                        }
353                    }
354
355                    None
356                }
357            }),
358        );
359
360        // Add a callback to detect "could not compile" errors
361        stderr_dispatcher.add_callback(
362            r"error: could not compile `(?P<crate_name>.+)` \((?P<due_to>.+)\) due to (?P<error_count>\d+) previous errors; (?P<warning_count>\d+) warnings emitted",
363            Box::new(|line, captures, _state, stats, _prior_response| {
364                println!("{}", line);
365            if let Some(caps) = captures {
366                // Extract dynamic fields from the error message
367                let crate_name = caps.name("crate_name").map(|m| m.as_str()).unwrap_or("unknown");
368                let due_to = caps.name("due_to").map(|m| m.as_str()).unwrap_or("unknown");
369                let error_count: usize = caps
370                .name("error_count")
371                .map(|m| m.as_str().parse().unwrap_or(0))
372                .unwrap_or(0);
373                let warning_count: usize = caps
374                .name("warning_count")
375                .map(|m| m.as_str().parse().unwrap_or(0))
376                .unwrap_or(0);
377
378                // Log the captured information (optional)
379                println!(
380                "Detected compilation failure: crate=`{}`, due_to=`{}`, errors={}, warnings={}",
381                crate_name, due_to, error_count, warning_count
382                );
383
384                // Set `is_could_not_compile` to true in the stats
385                let mut stats = stats.lock().unwrap();
386                stats.is_could_not_compile = true;
387            }
388            None // No callback response needed
389            }),
390        );
391
392        // Clone diagnostics_arc for this closure to avoid move
393        let diagnostics_arc_for_diag = Arc::clone(&diagnostics_arc);
394        stderr_dispatcher.add_callback(
395            r"^(?P<level>\w+)(\[(?P<error_code>E\d+)\])?:\s+(?P<msg>.+)$", // Regex for diagnostic line
396            Box::new(
397                move |_line, caps, _multiline_flag, _stats, _prior_response| {
398                    if let Some(caps) = caps {
399                        let mut counts = counts.lock().unwrap();
400                        // Create a PendingDiag and save the message
401                        let mut pending_diag = pending_d.lock().unwrap();
402                        let mut last_lineref = String::new();
403                        if let Some(existing_diag) = pending_diag.take() {
404                            let mut diags = diagnostics_arc_for_diag.lock().unwrap();
405                            last_lineref = existing_diag.lineref.clone();
406                            diags.push(existing_diag.clone());
407                        }
408                        log::trace!("Diagnostic line: {}", _line);
409                        let level = caps["level"].to_string(); // e.g., "warning", "error"
410                        let message = caps["msg"].to_string();
411                        // If the message contains "generated" followed by one or more digits,
412                        // then ignore this diagnostic by returning None.
413                        let re_generated = regex::Regex::new(r"generated\s+\d+").unwrap();
414                        if re_generated.is_match(&message) {
415                            log::trace!("Skipping generated diagnostic: {}", _line);
416                            return None;
417                        }
418
419                        let error_code = caps.name("error_code").map(|m| m.as_str().to_string());
420                        let diag_level = match level.as_str() {
421                            "error" => CargoDiagnosticLevel::Error,
422                            "warning" => CargoDiagnosticLevel::Warning,
423                            "help" => CargoDiagnosticLevel::Help,
424                            "note" => CargoDiagnosticLevel::Note,
425                            _ => {
426                                println!("Unknown diagnostic level: {}", level);
427                                return None; // Ignore unknown levels
428                            }
429                        };
430                        // Increment the count for this level
431                        *counts.entry(diag_level).or_insert(0) += 1;
432
433                        let current_count = counts.get(&diag_level).unwrap_or(&0);
434                        let diag = CargoDiagnostic {
435                            error_code: error_code.clone(),
436                            lineref: last_lineref.clone(),
437                            level: level.clone(),
438                            message,
439                            suggestion: None,
440                            help: None,
441                            note: None,
442                            uses_color: true,
443                            diag_num_padding: Some(2),
444                            diag_number: Some(*current_count),
445                        };
446
447                        // Save the new diagnostic
448                        *pending_diag = Some(diag);
449
450                        // Track the count of diagnostics for each level
451                        return Some(CallbackResponse {
452                            callback_type: CallbackType::LevelMessage, // Treat subsequent lines as warnings
453                            message: None,
454                            file: None,
455                            line: None,
456                            column: None,
457                            suggestion: None, // This is the suggestion part
458                            terminal_status: None,
459                        });
460                    } else {
461                        println!("No captures found in line: {}", _line);
462                        None
463                    }
464                },
465            ),
466        );
467        // Look-behind buffer for last 6 lines before backtrace
468        let look_behind = Arc::new(Mutex::new(Vec::<String>::new()));
469        {
470            let look_behind = Arc::clone(&look_behind);
471            // This callback runs for every stderr line to update the look-behind buffer
472            stderr_dispatcher.add_callback(
473                r"^(?P<msg>.*)$",
474                Box::new(move |line, _captures, _state, _stats, _prior_response| {
475                    let mut buf = look_behind.lock().unwrap();
476                    if line.trim().is_empty() {
477                        return None;
478                    }
479                    buf.push(line.to_string());
480                    if buf.len() > 6 {
481                        buf.remove(0);
482                    }
483                    None
484                }),
485            );
486        }
487
488        // --- Patch: Use look_behind before backtrace_lines in the note ---
489        {
490            let pending_diag = Arc::clone(&pending_diag);
491            let diagnostics_arc = Arc::clone(&diagnostics_arc);
492            let backtrace_mode = Arc::new(AtomicBool::new(false));
493            let backtrace_lines = Arc::new(Mutex::new(Vec::<String>::new()));
494            let look_behind = Arc::clone(&look_behind);
495            let stored_lines_behind = Arc::new(Mutex::new(Vec::<String>::new()));
496
497            // Enable backtrace mode when we see "stack backtrace:"
498            {
499                let backtrace_mode = Arc::clone(&backtrace_mode);
500                let backtrace_lines = Arc::clone(&backtrace_lines);
501                let stored_lines_behind = Arc::clone(&stored_lines_behind);
502                let look_behind = Arc::clone(&look_behind);
503                stderr_dispatcher.add_callback(
504                    r"stack backtrace:",
505                    Box::new(move |_line, _captures, _state, _stats, _prior_response| {
506                        backtrace_mode.store(true, Ordering::Relaxed);
507                        backtrace_lines.lock().unwrap().clear();
508                        // Save the current look_behind buffer into a shared stored_lines_behind for later use
509                        {
510                            let look_behind_buf = look_behind.lock().unwrap();
511                            let mut stored = stored_lines_behind.lock().unwrap();
512                            *stored = look_behind_buf.clone();
513                        }
514                        None
515                    }),
516                );
517            }
518
519            // Process backtrace lines, filter and summarize
520            {
521                let backtrace_mode = Arc::clone(&backtrace_mode);
522                let backtrace_lines = Arc::clone(&backtrace_lines);
523                let pending_diag = Arc::clone(&pending_diag);
524                let diagnostics_arc = Arc::clone(&diagnostics_arc);
525                let look_behind = Arc::clone(&look_behind);
526
527                // Regex for numbered backtrace line: "  0: type::path"
528                let re_number_type = Regex::new(r"^\s*(\d+):\s+(.*)$").unwrap();
529                // Regex for "at path:line"
530                let re_at_path = Regex::new(r"^\s*at\s+([^\s:]+):(\d+)").unwrap();
531
532                stderr_dispatcher.add_callback(
533                    r"^(?P<msg>.*)$",
534                    Box::new(
535                        move |mut line, _captures, _state, _stats, _prior_response| {
536                            if backtrace_mode.load(Ordering::Relaxed) {
537                                line = line.trim();
538                                // End of backtrace if empty line or new diagnostic/note
539                                if line.trim().is_empty()
540                                    || line.starts_with("note:")
541                                    || line.starts_with("error:")
542                                {
543                                    let mut bt_lines = Vec::new();
544                                    let mut skip_next = false;
545                                    let mut last_number_type: Option<(String, String)> = None;
546                                    for l in backtrace_lines.lock().unwrap().iter() {
547                                        if let Some(caps) = re_number_type.captures(l) {
548                                            // Save the (number, type) line, but don't push yet
549                                            last_number_type =
550                                                Some((caps[1].to_string(), caps[2].to_string()));
551                                            skip_next = true;
552                                        } else if skip_next && re_at_path.is_match(l) {
553                                            let path_caps = re_at_path.captures(l).unwrap();
554                                            let path = path_caps.get(1).unwrap().as_str();
555                                            let line_num = path_caps.get(2).unwrap().as_str();
556                                            if path.starts_with("/rustc")
557                                                || path.contains(".cargo")
558                                                || path.contains(".rustup")
559                                            {
560                                                // Skip both the number: type and the at line
561                                                // (do not push either)
562                                            } else {
563                                                // Push both the number: type and the at line, on the same line
564                                                if let Some((num, typ)) = last_number_type.take() {
565                                                    // Canonicalize the path if possible for better readability
566                                                    let path = match std::fs::canonicalize(path) {
567                                                        Ok(canon) => canon.display().to_string(),
568                                                        Err(_) => path.to_string(),
569                                                    };
570
571                                                    bt_lines.push(format!(
572                                                        "{}: {} @ {}:{}",
573                                                        num, typ, path, line_num
574                                                    ));
575                                                }
576                                            }
577                                            skip_next = false;
578                                        } else if let Some((num, typ)) = last_number_type.take() {
579                                            // If the previous number: type was not followed by an at line, push it
580                                            bt_lines.push(format!("{}: {}", num, typ));
581                                            if !l.trim().is_empty() {
582                                                bt_lines.push(l.clone());
583                                            }
584                                            skip_next = false;
585                                        } else if !l.trim().is_empty() {
586                                            bt_lines.push(l.clone());
587                                            skip_next = false;
588                                        }
589                                    }
590                                    if !bt_lines.is_empty() {
591                                        let mut pending_diag = pending_diag.lock().unwrap();
592                                        if let Some(ref mut diag) = *pending_diag {
593                                            // --- Insert stored_lines_behind lines before backtrace_lines ---
594                                            let stored_lines = {
595                                                let buf = stored_lines_behind.lock().unwrap();
596                                                buf.clone()
597                                            };
598                                            let note = diag.note.get_or_insert_with(String::new);
599                                            if !stored_lines.is_empty() {
600                                                note.push_str(&stored_lines.join("\n"));
601                                                note.push('\n');
602                                            }
603                                            note.push_str(&bt_lines.join("\n"));
604                                            let mut diags = diagnostics_arc.lock().unwrap();
605                                            diags.push(diag.clone());
606                                        }
607                                    }
608                                    backtrace_mode.store(false, Ordering::Relaxed);
609                                    backtrace_lines.lock().unwrap().clear();
610                                    return None;
611                                }
612
613                                // Only keep lines that are part of the backtrace
614                                if re_number_type.is_match(line) || re_at_path.is_match(line) {
615                                    backtrace_lines.lock().unwrap().push(line.to_string());
616                                }
617                                // Ignore further lines
618                                return None;
619                            }
620                            None
621                        },
622                    ),
623                );
624            }
625        }
626
627        // suggestion callback
628        {
629            let location_lock_clone = Arc::clone(&warning_location);
630            let suggestion_m = Arc::clone(&suggestion_mode);
631
632            // Suggestion callback that adds subsequent lines as suggestions
633            stderr_dispatcher.add_callback(
634                r"^(?P<msg>.*)$", // Capture all lines following the location
635                Box::new(
636                    move |line, _captures, _multiline_flag, _stats, _prior_response| {
637                        if suggestion_m.load(Ordering::Relaxed) {
638                            // Only process lines that match the suggestion format
639                            if let Some(caps) = suggestion_regex.captures(line.trim()) {
640                                // Capture the line number and code from the suggestion line
641                                // let line_num = caps[1].parse::<usize>().unwrap_or(0);
642                                let code = caps[2].to_string();
643
644                                // Lock the pending_diag to add the suggestion
645                                if let Ok(mut lock) = location_lock_clone.lock() {
646                                    if let Some(mut loc) = lock.take() {
647                                        // let file = loc.file.clone().unwrap_or_default();
648                                        // let col = loc.column.unwrap_or(0);
649
650                                        // Concatenate the suggestion line to the message
651                                        let mut msg = loc.message.unwrap_or_default();
652                                        msg.push_str(&format!("\n{}", code));
653
654                                        // Print the concatenated suggestion for debugging
655                                        // println!("daveSuggestion for {}:{}:{} - {}", file, line_num, col, msg);
656
657                                        // Update the location with the new concatenated message
658                                        loc.message = Some(msg.clone());
659                                        // println!("Updating location lock with new suggestion: {}", msg);
660                                        // Save the updated location back to shared state
661                                        // if let Ok(mut lock) = location_lock_clone.lock() {
662                                        // println!("Updating location lock with new suggestion: {}", msg);
663                                        lock.replace(loc);
664                                        // } else {
665                                        //     eprintln!("Failed to acquire lock for location_lock_clone");
666                                        // }
667                                    }
668                                    // return Some(CallbackResponse {
669                                    //     callback_type: CallbackType::Warning, // Treat subsequent lines as warnings
670                                    //     message: Some(msg.clone()),
671                                    //     file: Some(file),
672                                    //     line: Some(line_num),
673                                    //     column: Some(col),
674                                    //     suggestion: Some(msg),  // This is the suggestion part
675                                    //     terminal_status: None,
676                                    // });
677                                }
678                            }
679                        } else {
680                            // println!("Suggestion mode is not active. Ignoring line: {}", line);
681                        }
682
683                        None
684                    },
685                ),
686            );
687        }
688        {
689            let suggestion_m = Arc::clone(&suggestion_mode);
690            let pending_diag_clone = Arc::clone(&pending_diag);
691            let diagnostics_arc = Arc::clone(&self.diagnostics);
692            // Callback for handling when an empty line or new diagnostic is received
693            stderr_dispatcher.add_callback(
694                r"^\s*$", // Regex to capture empty line
695                Box::new(
696                    move |_line, _captures, _multiline_flag, _stats, _prior_response| {
697                        // println!("Empty line detected: {}", line);
698                        suggestion_m.store(false, Ordering::Relaxed);
699                        // End of current diagnostic: take and process it.
700                        if let Some(pending_diag) = pending_diag_clone.lock().unwrap().take() {
701                            //println!("{:?}", pending_diag);
702                            // Use diagnostics_arc instead of self.diagnostices
703                            let mut diags = diagnostics_arc.lock().unwrap();
704                            diags.push(pending_diag.clone());
705                        } else {
706                            // println!("No pending diagnostic to process.");
707                        }
708                        // Handle empty line scenario to end the current diagnostic processing
709                        // if let Some(pending_diag) = pending_diag_clone.lock().unwrap().take() {
710                        //     println!("{:?}", pending_diag);
711                        //     let mut diags = self.diagnostics.lock().unwrap();
712                        //     diags.push(pending_diag.clone());
713                        //                             // let diag = crate::e_eventdispatcher::convert_message_to_diagnostic(msg, &msg_str);
714                        //                             // diags.push(diag.clone());
715                        //                             // if let Some(ref sd) = stage_disp_clone {
716                        //                             //     sd.dispatch(&format!("Stage: Diagnostic occurred at {:?}", now));
717                        //                             // }
718                        //     // Handle the saved PendingDiag and its CallbackResponse
719                        //     // if let Some(callback_response) = pending_diag.callback_response {
720                        //     //     println!("End of Diagnostic: {:?}", callback_response);
721                        //     // }
722                        // } else {
723                        //     println!("No pending diagnostic to process.");
724                        // }
725
726                        None
727                    },
728                ),
729            );
730        }
731
732        // {
733        //     let pending_diag = Arc::clone(&pending_diag);
734        //     let location_lock = Arc::clone(&warning_location);
735        //     let suggestion_m = Arc::clone(&suggestion_mode);
736
737        // let suggestion_regex = Regex::new(r"^\s*(\d+)\s*\|\s*(.*)$").unwrap();
738
739        //     stderr_dispatcher.add_callback(
740        //     r"^\s*(\d+)\s*\|\s*(.*)$",  // Match suggestion line format
741        //     Box::new(move |line, _captures, _multiline_flag| {
742        //         if suggestion_m.load(Ordering::Relaxed) {
743        //             // Only process lines that match the suggestion format
744        //             if let Some(caps) = suggestion_regex.captures(line.trim()) {
745        //                 // Capture the line number and code from the suggestion line
746        //                 let line_num = caps[1].parse::<usize>().unwrap_or(0);
747        //                 let code = caps[2].to_string();
748
749        //                 // Lock the pending_diag to add the suggestion
750        //                 if let Some(mut loc) = location_lock.lock().unwrap().take() {
751        //                     println!("Suggestion line: {}", line);
752        //                     let file = loc.file.clone().unwrap_or_default();
753        //                     let col = loc.column.unwrap_or(0);
754
755        //                     // Concatenate the suggestion line to the message
756        //                     let mut msg = loc.message.unwrap_or_default();
757        //                     msg.push_str(&format!("\n{} | {}", line_num, code));  // Append the suggestion properly
758
759        //                     // Print the concatenated suggestion for debugging
760        //                     println!("Suggestion for {}:{}:{} - {}", file, line_num, col, msg);
761
762        //                     // Update the location with the new concatenated message
763        //                     loc.message = Some(msg.clone());
764
765        //                     // Save the updated location back to shared state
766        //                     location_lock.lock().unwrap().replace(loc);
767
768        //                     // return Some(CallbackResponse {
769        //                     //     callback_type: CallbackType::Warning, // Treat subsequent lines as warnings
770        //                     //     message: Some(msg.clone()),
771        //                     //     file: Some(file),
772        //                     //     line: Some(line_num),
773        //                     //     column: Some(col),
774        //                     //     suggestion: Some(msg),  // This is the suggestion part
775        //                     //     terminal_status: None,
776        //                     // });
777        //                 } else {
778        //                     println!("No location information available for suggestion line: {}", line);
779        //                 }
780        //             } else {
781        //                 println!("Suggestion line does not match expected format: {}", line);
782        //             }
783        //         } else {
784        //             println!("Suggestion mode is not active. Ignoring line: {}", line);
785        //         }
786
787        //         None
788        //     }),
789        // );
790
791        // }
792
793        {
794            let location_lock = Arc::clone(&warning_location);
795            let pending_diag = Arc::clone(&pending_diag);
796            let suggestion_mode = Arc::clone(&suggestion_mode);
797            stderr_dispatcher.add_callback(
798                r"^(?P<msg>.*)$", // Capture all lines following the location
799                Box::new(
800                    move |line, _captures, _multiline_flag, _stats, _prior_response| {
801                        // Lock the location to fetch the original diagnostic info
802                        if let Ok(location_guard) = location_lock.lock() {
803                            if let Some(loc) = location_guard.as_ref() {
804                                let file = loc.file.clone().unwrap_or_default();
805                                let line_num = loc.line.unwrap_or(0);
806                                let col = loc.column.unwrap_or(0);
807                                // println!("SUGGESTION: Suggestion for {}:{}:{} {}", file, line_num, col, line);
808
809                                // Only treat lines starting with | or numbers as suggestion lines
810                                if line.trim().starts_with('|')
811                                    || line.trim().starts_with(char::is_numeric)
812                                {
813                                    // Get the existing suggestion and append the new line
814                                    let suggestion = line.trim();
815
816                                    // Print the suggestion for debugging
817                                    // println!("Suggestion for {}:{}:{} - {}", file, line_num, col, suggestion);
818
819                                    // Lock the pending_diag and update its callback_response field
820                                    let mut pending_diag = match pending_diag.lock() {
821                                        Ok(lock) => lock,
822                                        Err(e) => {
823                                            eprintln!("Failed to acquire lock: {}", e);
824                                            return None; // Handle the error appropriately
825                                        }
826                                    };
827                                    if let Some(diag) = pending_diag.take() {
828                                        // If a PendingDiag already exists, update the existing callback response with the new suggestion
829                                        let mut diag = diag;
830
831                                        // Append the new suggestion to the existing one
832                                        if let Some(ref mut existing) = diag.suggestion {
833                                            diag.suggestion =
834                                                Some(format!("{}\n{}", existing, suggestion));
835                                        } else {
836                                            diag.suggestion = Some(suggestion.to_string());
837                                        }
838
839                                        // Update the shared state with the new PendingDiag
840                                        *pending_diag = Some(diag.clone());
841                                        return Some(CallbackResponse {
842                                            callback_type: CallbackType::Suggestion, // Treat subsequent lines as warnings
843                                            message: Some(
844                                                diag.clone().suggestion.clone().unwrap().clone(),
845                                            ),
846                                            file: Some(file),
847                                            line: Some(line_num),
848                                            column: Some(col),
849                                            suggestion: diag.clone().suggestion.clone(), // This is the suggestion part
850                                            terminal_status: None,
851                                        });
852                                    } else {
853                                        // println!("No pending diagnostic to process for suggestion line: {}", line);
854                                    }
855                                } else {
856                                    // If the line doesn't match the suggestion format, just return it as is
857                                    if line.trim().is_empty() {
858                                        // Ignore empty lines
859                                        suggestion_mode.store(false, Ordering::Relaxed);
860                                        return None;
861                                    }
862                                }
863                            } else {
864                                // println!("No location information available for suggestion line: {}", line);
865                            }
866                        }
867                        None
868                    },
869                ),
870            );
871        }
872
873        // 2) Location callback stores its response into that shared state
874        {
875            let pending_diag = Arc::clone(&pending_diag);
876            let warning_location = Arc::clone(&warning_location);
877            let location_lock = Arc::clone(&warning_location);
878            let suggestion_mode = Arc::clone(&suggestion_mode);
879            let manifest_path = self.manifest_path.clone();
880            stderr_dispatcher.add_callback(
881                // r"^\s*-->\s+(?P<file>[^:]+):(?P<line>\d+):(?P<col>\d+)$",
882                r"^\s*-->\s+(?P<file>.+?)(?::(?P<line>\d+))?(?::(?P<col>\d+))?\s*$",
883                Box::new(
884                    move |_line, caps, _multiline_flag, _stats, _prior_response| {
885                        log::trace!("Location line: {}", _line);
886                        // if multiline_flag.load(Ordering::Relaxed) {
887                        if let Some(caps) = caps {
888                            let file = caps["file"].to_string();
889                            let resolved_path = resolve_file_path(&manifest_path, &file);
890                            let file = resolved_path.to_str().unwrap_or_default().to_string();
891                            let line = caps["line"].parse::<usize>().unwrap_or(0);
892                            let column = caps["col"].parse::<usize>().unwrap_or(0);
893                            let resp = CallbackResponse {
894                                callback_type: CallbackType::Location,
895                                message: format!("{}:{}:{}", file, line, column).into(),
896                                file: Some(file.clone()),
897                                line: Some(line),
898                                column: Some(column),
899                                suggestion: None,
900                                terminal_status: None,
901                            };
902                            // Lock the pending_diag and update its callback_response field
903                            let mut pending_diag = pending_diag.lock().unwrap();
904                            if let Some(diag) = pending_diag.take() {
905                                // If a PendingDiag already exists, save the new callback response in the existing PendingDiag
906                                let mut diag = diag;
907                                diag.lineref = format!("{}:{}:{}", file, line, column); // Update the lineref
908                                                                                        // diag.save_callback_response(resp.clone()); // Save the callback response
909                                                                                        // Update the shared state with the new PendingDiag
910                                *pending_diag = Some(diag);
911                            }
912                            // Save it for the generic callback to see
913                            *warning_location.lock().unwrap() = Some(resp.clone());
914                            *location_lock.lock().unwrap() = Some(resp.clone());
915                            // Set suggestion mode to true as we've encountered a location line
916                            suggestion_mode.store(true, Ordering::Relaxed);
917                            return Some(resp.clone());
918                        } else {
919                            println!("No captures found in line: {}", _line);
920                        }
921                        // }
922                        None
923                    },
924                ),
925            );
926        }
927
928        // // 3) Note callback — attach note to pending_diag
929        {
930            let pending_diag = Arc::clone(&pending_diag);
931            stderr_dispatcher.add_callback(
932                r"^\s*=\s*note:\s*(?P<msg>.+)$",
933                Box::new(move |_line, caps, _state, _stats, _prior_response| {
934                    if let Some(caps) = caps {
935                        let mut pending_diag = pending_diag.lock().unwrap();
936                        if let Some(ref mut resp) = *pending_diag {
937                            // Prepare the new note with the blue prefix
938                            let new_note = format!("note: {}", caps["msg"].to_string());
939
940                            // Append or set the note
941                            if let Some(existing_note) = &resp.note {
942                                // If there's already a note, append with newline and the new note
943                                resp.note = Some(format!("{}\n{}", existing_note, new_note));
944                            } else {
945                                // If no existing note, just set the new note
946                                resp.note = Some(new_note);
947                            }
948                        }
949                    }
950                    None
951                }),
952            );
953        }
954
955        // 4) Help callback — attach help to pending_diag
956        {
957            let pending_diag = Arc::clone(&pending_diag);
958            stderr_dispatcher.add_callback(
959                r"^\s*(?:\=|\|)\s*help:\s*(?P<msg>.+)$", // Regex to match both '=' and '|' before help:
960                Box::new(move |_line, caps, _state, _stats, _prior_response| {
961                    if let Some(caps) = caps {
962                        let mut pending_diag = pending_diag.lock().unwrap();
963                        if let Some(ref mut resp) = *pending_diag {
964                            // Create the new help message with the orange "h:" prefix
965                            let new_help =
966                                format!("\x1b[38;5;214mhelp: {}\x1b[0m", caps["msg"].to_string());
967
968                            // Append or set the help message
969                            if let Some(existing_help) = &resp.help {
970                                // If there's already a help message, append with newline
971                                resp.help = Some(format!("{}\n{}", existing_help, new_help));
972                            } else {
973                                // If no existing help message, just set the new one
974                                resp.help = Some(new_help);
975                            }
976                        }
977                    }
978                    None
979                }),
980            );
981        }
982
983        stderr_dispatcher.add_callback(
984    r"(?:\x1b\[[0-9;]*[A-Za-z])*\s*Serving(?:\x1b\[[0-9;]*[A-Za-z])*\s+at\s+(http://[^\s]+)",
985    Box::new(|line, captures, _state, stats , _prior_response| {
986        if let Some(caps) = captures {
987            let url = caps.get(1).unwrap().as_str();
988            let url = url.replace("0.0.0.0", "127.0.0.1");
989            println!("(STDERR) Captured URL: {}", url);
990            match open::that_detached(&url) {
991                Ok(_) => println!("(STDERR) Opened URL: {}",&url),
992                Err(e) => eprintln!("(STDERR) Failed to open URL: {}. Error: {:?}", url, e),
993            }
994             let mut stats = stats.lock().unwrap();
995             if stats.build_finished_time.is_none() {
996              let now = SystemTime::now();
997             stats.build_finished_time = Some(now);
998             }
999            Some(CallbackResponse {
1000                callback_type: CallbackType::OpenedUrl, // Choose as appropriate
1001                message: Some(format!("Captured and opened URL: {}", url)),
1002                file: None,
1003                line: None,
1004                column: None,
1005                suggestion: None,
1006                terminal_status: None,
1007            })
1008        } else {
1009            println!("(STDERR) No URL captured in line: {}", line);
1010            None
1011        }
1012    }),
1013);
1014
1015        let finished_flag = Arc::new(AtomicBool::new(false));
1016
1017        // 0) Finished‐profile summary callback
1018        {
1019            let finished_flag = Arc::clone(&finished_flag);
1020            stderr_dispatcher.add_callback(
1021        r"^Finished\s+`(?P<profile>[^`]+)`\s+profile\s+\[(?P<opts>[^\]]+)\]\s+target\(s\)\s+in\s+(?P<dur>[0-9.]+s)$",
1022        Box::new(move |_line, caps, _multiline_flag, stats, _prior_response | {
1023            if let Some(caps) = caps {
1024                finished_flag.store(true, Ordering::Relaxed);
1025                let profile = &caps["profile"];
1026                let opts    = &caps["opts"];
1027                let dur     = &caps["dur"];
1028                             let mut stats = stats.lock().unwrap();
1029             if stats.build_finished_time.is_none() {
1030              let now = SystemTime::now();
1031             stats.build_finished_time = Some(now);
1032             }
1033                Some(CallbackResponse {
1034                    callback_type: CallbackType::Note,
1035                    message: Some(format!("Finished `{}` [{}] in {}", profile, opts, dur)),
1036                    file: None, line: None, column: None, suggestion: None, terminal_status: None,
1037                })
1038            } else {
1039                None
1040            }
1041        }),
1042    );
1043        }
1044
1045        let summary_flag = Arc::new(AtomicBool::new(false));
1046        {
1047            let summary_flag = Arc::clone(&summary_flag);
1048            stderr_dispatcher.add_callback(
1049    r"^(?P<level>warning|error):\s+`(?P<name>[^`]+)`\s+\((?P<otype>lib|bin)\)\s+generated\s+(?P<count>\d+)\s+(?P<kind>warnings|errors).*run\s+`(?P<cmd>[^`]+)`\s+to apply\s+(?P<fixes>\d+)\s+suggestions",
1050    Box::new(move |_line, caps, multiline_flag, _stats, _prior_response | {
1051        let summary_flag = Arc::clone(&summary_flag);
1052        if let Some(caps) = caps {
1053            summary_flag.store(true, Ordering::Relaxed);
1054            // Always start fresh
1055            multiline_flag.store(false, Ordering::Relaxed);
1056
1057            let level    = &caps["level"];
1058            let name     = &caps["name"];
1059            let otype    = &caps["otype"];
1060            let count: usize = caps["count"].parse().unwrap_or(0);
1061            let kind     = &caps["kind"];   // "warnings" or "errors"
1062            let cmd      = caps["cmd"].to_string();
1063            let fixes: usize = caps["fixes"].parse().unwrap_or(0);
1064
1065            println!("SUMMARIZATION CALLBACK {}",
1066                    &format!("{}: `{}` ({}) generated {} {}; run `{}` to apply {} fixes",
1067                    level, name, otype, count, kind, cmd, fixes));
1068            Some(CallbackResponse {
1069                callback_type: CallbackType::Note,  // treat as informational
1070                message: Some(format!(
1071                    "{}: `{}` ({}) generated {} {}; run `{}` to apply {} fixes",
1072                    level, name, otype, count, kind, cmd, fixes
1073                )),
1074                file: None,
1075                line: None,
1076                column: None,
1077                suggestion: Some(cmd),
1078                terminal_status: None,
1079            })
1080        } else {
1081            None
1082        }
1083    }),
1084    );
1085        }
1086
1087        // {
1088        //     let summary_flag = Arc::clone(&summary_flag);
1089        //     let finished_flag = Arc::clone(&finished_flag);
1090        //     let warning_location = Arc::clone(&warning_location);
1091        //     // Warning callback for stdout.
1092        //     stderr_dispatcher.add_callback(
1093        //         r"^warning:\s+(?P<msg>.+)$",
1094        //         Box::new(
1095        //             move |line: &str, captures: Option<regex::Captures>, multiline_flag: Arc<AtomicBool>| {
1096        //                             // If summary or finished just matched, skip
1097        //             if summary_flag.swap(false, Ordering::Relaxed)
1098        //                 || finished_flag.swap(false, Ordering::Relaxed)
1099        //             {
1100        //                 return None;
1101        //             }
1102
1103        //         // 2) If this line *matches* the warning regex, handle as a new warning
1104        //         if let Some(caps) = captures {
1105        //             let msg = caps.name("msg").unwrap().as_str().to_string();
1106        //                    // 1) If a location was saved, print file:line:col – msg
1107        //             // println!("*WARNING detected: {:?}", msg);
1108        //                 multiline_flag.store(true, Ordering::Relaxed);
1109        //         if let Some(loc) = warning_location.lock().unwrap().take() {
1110        //                 let file = loc.file.unwrap_or_default();
1111        //                 let line_num = loc.line.unwrap_or(0);
1112        //                 let col  = loc.column.unwrap_or(0);
1113        //                 println!("{}:{}:{} - {}", file, line_num, col, msg);
1114        //                 return Some(CallbackResponse {
1115        //                     callback_type: CallbackType::Warning,
1116        //                     message: Some(msg.to_string()),
1117        //                     file: None, line: None, column: None, suggestion: None, terminal_status: None,
1118        //                 });
1119        //         }
1120        //             return Some(CallbackResponse {
1121        //                 callback_type: CallbackType::Warning,
1122        //                 message: Some(msg),
1123        //                 file: None,
1124        //                 line: None,
1125        //                 column: None,
1126        //                 suggestion: None,
1127        //                 terminal_status: None,
1128        //             });
1129        //         }
1130
1131        //                 // 3) Otherwise, if we’re in multiline mode, treat as continuation
1132        //         if multiline_flag.load(Ordering::Relaxed) {
1133        //             let text = line.trim();
1134        //             if text.is_empty() {
1135        //                 multiline_flag.store(false, Ordering::Relaxed);
1136        //                 return None;
1137        //             }
1138        //             // println!("   - {:?}", text);
1139        //             return Some(CallbackResponse {
1140        //                 callback_type: CallbackType::Warning,
1141        //                 message: Some(text.to_string()),
1142        //                 file: None,
1143        //                 line: None,
1144        //                 column: None,
1145        //                 suggestion: None,
1146        //                 terminal_status: None,
1147        //             });
1148        //         }
1149        //                     None
1150        //             },
1151        //         ),
1152        //     );
1153        // }
1154
1155        stderr_dispatcher.add_callback(
1156            r"IO\(Custom \{ kind: NotConnected",
1157            Box::new(move |line, _captures, _state, _stats, _prior_response| {
1158                println!("(STDERR) Terminal error detected: {:?}", &line);
1159                let result = if line.contains("NotConnected") {
1160                    TerminalError::NoTerminal
1161                } else {
1162                    TerminalError::NoError
1163                };
1164                let sender = sender.lock().unwrap();
1165                sender.send(result).ok();
1166                Some(CallbackResponse {
1167                    callback_type: CallbackType::Warning, // Choose as appropriate
1168                    message: Some(format!("Terminal Error: {}", line)),
1169                    file: None,
1170                    line: None,
1171                    column: None,
1172                    suggestion: None,
1173                    terminal_status: None,
1174                })
1175            }),
1176        );
1177        stderr_dispatcher.add_callback(
1178            r".*",
1179            Box::new(|line, _captures, _state, _stats, _prior_response| {
1180                log::trace!("stdraw[{:?}]", line);
1181                None // We're just printing, so no callback response is needed.
1182            }),
1183        );
1184        // need to implement autosense/filtering for tool installers; TBD
1185        // stderr_dispatcher.add_callback(
1186        //     r"Command 'perl' not found\. Is perl installed\?",
1187        //     Box::new(|line, _captures, _state, stats| {
1188        //     println!("cargo e sees a perl issue; maybe a prompt in the future or auto-resolution.");
1189        //     crate::e_autosense::auto_sense_perl();
1190        //     None
1191        //     }),
1192        // );
1193        // need to implement autosense/filtering for tool installers; TBD
1194        // stderr_dispatcher.add_callback(
1195        //     r"Error configuring OpenSSL build:\s+Command 'perl' not found\. Is perl installed\?",
1196        //     Box::new(|line, _captures, _state, stats| {
1197        //     println!("Detected OpenSSL build error due to missing 'perl'. Attempting auto-resolution.");
1198        //     crate::e_autosense::auto_sense_perl();
1199        //     None
1200        //     }),
1201        // );
1202        self.stderr_dispatcher = Some(Arc::new(stderr_dispatcher));
1203
1204        // let mut progress_dispatcher = EventDispatcher::new();
1205        // progress_dispatcher.add_callback(r"Progress", Box::new(|line, _captures,_state| {
1206        //     println!("(Progress) {}", line);
1207        //     None
1208        // }));
1209        // self.progress_dispatcher = Some(Arc::new(progress_dispatcher));
1210
1211        // let mut stage_dispatcher = EventDispatcher::new();
1212        // stage_dispatcher.add_callback(r"Stage:", Box::new(|line, _captures, _state| {
1213        //     println!("(Stage) {}", line);
1214        //     None
1215        // }));
1216        // self.stage_dispatcher = Some(Arc::new(stage_dispatcher));
1217    }
1218
1219    pub fn run<F>(self: Arc<Self>, on_spawn: F) -> anyhow::Result<u32>
1220    where
1221        F: FnOnce(u32, CargoProcessHandle),
1222    {
1223        if !self.is_filter {
1224            return self.switch_to_passthrough_mode(on_spawn);
1225        }
1226        let mut command = self.build_command();
1227
1228        let mut cargo_process_handle = command.spawn_cargo_capture(
1229            self.clone(),
1230            self.stdout_dispatcher.clone(),
1231            self.stderr_dispatcher.clone(),
1232            self.progress_dispatcher.clone(),
1233            self.stage_dispatcher.clone(),
1234            None,
1235        );
1236        cargo_process_handle.diagnostics = Arc::clone(&self.diagnostics);
1237        let pid = cargo_process_handle.pid;
1238
1239        // Notify observer
1240        on_spawn(pid, cargo_process_handle);
1241
1242        Ok(pid)
1243    }
1244
1245    // pub fn run(self: Arc<Self>) -> anyhow::Result<u32> {
1246    //     // Build the command using the builder's configuration
1247    //     let mut command = self.build_command();
1248
1249    //     // Spawn the cargo process handle
1250    //     let cargo_process_handle = command.spawn_cargo_capture(
1251    //         self.stdout_dispatcher.clone(),
1252    //         self.stderr_dispatcher.clone(),
1253    //         self.progress_dispatcher.clone(),
1254    //         self.stage_dispatcher.clone(),
1255    //         None,
1256    //     );
1257    // let pid = cargo_process_handle.pid;
1258    // let mut global = GLOBAL_CHILDREN.lock().unwrap();
1259    // global.insert(pid, Arc::new(Mutex::new(cargo_process_handle)));
1260    //     Ok(pid)
1261    // }
1262
1263    pub fn wait(self: Arc<Self>, pid: Option<u32>) -> anyhow::Result<CargoProcessResult> {
1264        let mut global = GLOBAL_CHILDREN.lock().unwrap();
1265        if let Some(pid) = pid {
1266            // Lock the global list of processes and attempt to find the cargo process handle directly by pid
1267            if let Some(cargo_process_handle) = global.get_mut(&pid) {
1268                let mut cargo_process_handle = cargo_process_handle.lock().unwrap();
1269
1270                // Wait for the process to finish and retrieve the result
1271                // println!("Waiting for process with PID: {}", pid);
1272                // let result = cargo_process_handle.wait();
1273                // println!("Process with PID {} finished", pid);
1274                loop {
1275                    println!("Waiting for process with PID: {}", pid);
1276
1277                    // Attempt to wait for the process, but don't block indefinitely
1278                    let status = cargo_process_handle.child.try_wait()?;
1279
1280                    // If the status is `Some(status)`, the process has finished
1281                    if let Some(status) = status {
1282                        if status.code() == Some(101) {
1283                            println!("Process with PID {} finished with cargo error", pid);
1284                        }
1285
1286                        // Check the terminal error flag and update the result if there is an error
1287                        if *cargo_process_handle.terminal_error_flag.lock().unwrap()
1288                            != TerminalError::NoError
1289                        {
1290                            let terminal_error =
1291                                *cargo_process_handle.terminal_error_flag.lock().unwrap();
1292                            cargo_process_handle.result.terminal_error = Some(terminal_error);
1293                        }
1294
1295                        let final_diagnostics = {
1296                            let diag_lock = self.diagnostics.lock().unwrap();
1297                            diag_lock.clone()
1298                        };
1299                        cargo_process_handle.result.diagnostics = final_diagnostics.clone();
1300                        cargo_process_handle.result.exit_status = Some(status);
1301                        cargo_process_handle.result.end_time = Some(SystemTime::now());
1302                        cargo_process_handle.result.elapsed_time = Some(
1303                            cargo_process_handle
1304                                .result
1305                                .end_time
1306                                .unwrap()
1307                                .duration_since(cargo_process_handle.result.start_time.unwrap())
1308                                .unwrap(),
1309                        );
1310                        println!(
1311                            "Process with PID {} finished {:?} {}",
1312                            pid,
1313                            status,
1314                            final_diagnostics.len()
1315                        );
1316                        return Ok(cargo_process_handle.result.clone());
1317                        // return Ok(CargoProcessResult { exit_status: status, ..Default::default() });
1318                    }
1319
1320                    // Sleep briefly to yield control back to the system and avoid blocking
1321                    std::thread::sleep(std::time::Duration::from_secs(1));
1322                }
1323
1324                // Return the result
1325                // match result {
1326                //     Ok(res) => Ok(res),
1327                //     Err(e) => Err(anyhow::anyhow!("Failed to wait for cargo process: {}", e).into()),
1328                // }
1329            } else {
1330                Err(anyhow::anyhow!(
1331                    "Process handle with PID {} not found in GLOBAL_CHILDREN",
1332                    pid
1333                )
1334                .into())
1335            }
1336        } else {
1337            Err(anyhow::anyhow!("No PID provided for waiting on cargo process").into())
1338        }
1339    }
1340
1341    // pub fn run_wait(self: Arc<Self>) -> anyhow::Result<CargoProcessResult> {
1342    //     // Run the cargo command and get the process handle (non-blocking)
1343    //     let pid = self.clone().run()?; // adds to global list of processes
1344    //     let result = self.wait(Some(pid)); // Wait for the process to finish
1345    //     // Remove the completed process from GLOBAL_CHILDREN
1346    //     let mut global = GLOBAL_CHILDREN.lock().unwrap();
1347    //     global.remove(&pid);
1348
1349    //     result
1350    // }
1351
1352    // Runs the cargo command using the builder's configuration.
1353    // pub fn run(&self) -> anyhow::Result<CargoProcessResult> {
1354    //     // Build the command using the builder's configuration
1355    //     let mut command = self.build_command();
1356
1357    //     // Now use the `spawn_cargo_capture` extension to run the command
1358    //     let mut cargo_process_handle = command.spawn_cargo_capture(
1359    //         self.stdout_dispatcher.clone(),
1360    //         self.stderr_dispatcher.clone(),
1361    //         self.progress_dispatcher.clone(),
1362    //         self.stage_dispatcher.clone(),
1363    //         None,
1364    //     );
1365
1366    //     // Wait for the process to finish and retrieve the results
1367    //     cargo_process_handle.wait().context("Failed to execute cargo process")
1368    // }
1369
1370    /// Configure the command based on the target kind.
1371    pub fn with_target(mut self, target: &CargoTarget) -> Self {
1372        if let Some(origin) = target.origin.clone() {
1373            println!("Target origin: {:?}", origin);
1374        } else {
1375            println!("Target origin is not set");
1376        }
1377        match target.kind {
1378            TargetKind::Unknown | TargetKind::Plugin => {
1379                return self;
1380            }
1381            TargetKind::Bench => {
1382                // // To run benchmarks, use the "bench" command.
1383                //  let exe_path = match which("bench") {
1384                //     Ok(path) => path,
1385                //     Err(err) => {
1386                //         eprintln!("Error: 'trunk' not found in PATH: {}", err);
1387                //         return self;
1388                //     }
1389                // };
1390                // self.alternate_cmd = Some("bench".to_string())
1391                self.args.push("bench".into());
1392                self.args.push(target.name.clone());
1393            }
1394            TargetKind::Test => {
1395                self.args.push("test".into());
1396                // Pass the target's name as a filter to run specific tests.
1397                self.args.push(target.name.clone());
1398            }
1399            TargetKind::UnknownExample
1400            | TargetKind::UnknownExtendedExample
1401            | TargetKind::Example
1402            | TargetKind::ExtendedExample => {
1403                self.args.push(self.subcommand.clone());
1404                //self.args.push("--message-format=json".into());
1405                self.args.push("--example".into());
1406                self.args.push(target.name.clone());
1407                self.args.push("--manifest-path".into());
1408                self.args.push(
1409                    target
1410                        .manifest_path
1411                        .clone()
1412                        .to_str()
1413                        .unwrap_or_default()
1414                        .to_owned(),
1415                );
1416                self = self.with_required_features(&target.manifest_path, target);
1417            }
1418            TargetKind::UnknownBinary
1419            | TargetKind::UnknownExtendedBinary
1420            | TargetKind::Binary
1421            | TargetKind::ExtendedBinary => {
1422                self.args.push(self.subcommand.clone());
1423                self.args.push("--bin".into());
1424                self.args.push(target.name.clone());
1425                self.args.push("--manifest-path".into());
1426                self.args.push(
1427                    target
1428                        .manifest_path
1429                        .clone()
1430                        .to_str()
1431                        .unwrap_or_default()
1432                        .to_owned(),
1433                );
1434                self = self.with_required_features(&target.manifest_path, target);
1435            }
1436            TargetKind::Manifest => {
1437                self.suppressed_flags.insert("quiet".to_string());
1438                self.args.push(self.subcommand.clone());
1439                self.args.push("--manifest-path".into());
1440                self.args.push(
1441                    target
1442                        .manifest_path
1443                        .clone()
1444                        .to_str()
1445                        .unwrap_or_default()
1446                        .to_owned(),
1447                );
1448            }
1449            TargetKind::ManifestTauriExample => {
1450                self.suppressed_flags.insert("quiet".to_string());
1451                self.args.push(self.subcommand.clone());
1452                self.args.push("--example".into());
1453                self.args.push(target.name.clone());
1454                self.args.push("--manifest-path".into());
1455                self.args.push(
1456                    target
1457                        .manifest_path
1458                        .clone()
1459                        .to_str()
1460                        .unwrap_or_default()
1461                        .to_owned(),
1462                );
1463                self = self.with_required_features(&target.manifest_path, target);
1464            }
1465            TargetKind::ScriptScriptisto => {
1466                let exe_path = match which("scriptisto") {
1467                    Ok(path) => path,
1468                    Err(err) => {
1469                        eprintln!("Error: 'scriptisto' not found in PATH: {}", err);
1470                        return self;
1471                    }
1472                };
1473                self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
1474                let candidate_opt = match &target.origin {
1475                    Some(TargetOrigin::SingleFile(path))
1476                    | Some(TargetOrigin::DefaultBinary(path)) => Some(path),
1477                    _ => None,
1478                };
1479                if let Some(candidate) = candidate_opt {
1480                    self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
1481                    self.args.push(candidate.to_string_lossy().to_string());
1482                } else {
1483                    println!("No scriptisto origin found for: {:?}", target);
1484                }
1485            }
1486            TargetKind::ScriptRustScript => {
1487                let exe_path = match crate::e_installer::ensure_rust_script() {
1488                    Ok(p) => p,
1489                    Err(e) => {
1490                        eprintln!("{}", e);
1491                        return self;
1492                    }
1493                };
1494                let candidate_opt = match &target.origin {
1495                    Some(TargetOrigin::SingleFile(path))
1496                    | Some(TargetOrigin::DefaultBinary(path)) => Some(path),
1497                    _ => None,
1498                };
1499                if let Some(candidate) = candidate_opt {
1500                    self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
1501                    if self.is_filter {
1502                        self.args.push("-c".into()); // ask for cargo output
1503                    }
1504                    self.args.push(candidate.to_string_lossy().to_string());
1505                } else {
1506                    println!("No rust-script origin found for: {:?}", target);
1507                }
1508            }
1509            TargetKind::ManifestTauri => {
1510                // First, locate the Cargo.toml using the existing function
1511                let manifest_path = crate::locate_manifest(true).unwrap_or_else(|_| {
1512                    eprintln!("Error: Unable to locate Cargo.toml file.");
1513                    std::process::exit(1);
1514                });
1515
1516                // Now, get the workspace parent from the manifest directory
1517                let manifest_dir = Path::new(&manifest_path)
1518                    .parent()
1519                    .unwrap_or(Path::new(".."));
1520
1521                // Ensure npm dependencies are handled at the workspace parent level
1522                let pnpm =
1523                    crate::e_installer::check_pnpm_and_install(manifest_dir).unwrap_or_else(|_| {
1524                        eprintln!("Error: Unable to check pnpm dependencies.");
1525                        PathBuf::new()
1526                    });
1527                if pnpm == PathBuf::new() {
1528                    crate::e_installer::check_npm_and_install(manifest_dir).unwrap_or_else(|_| {
1529                        eprintln!("Error: Unable to check npm dependencies.");
1530                    });
1531                }
1532
1533                self.suppressed_flags.insert("quiet".to_string());
1534                // Helper closure to check for tauri.conf.json in a directory.
1535                let has_tauri_conf = |dir: &Path| -> bool { dir.join("tauri.conf.json").exists() };
1536
1537                // Helper closure to check for tauri.conf.json and package.json in a directory.
1538                let has_file = |dir: &Path, filename: &str| -> bool { dir.join(filename).exists() };
1539                // Try candidate's parent (if origin is SingleFile or DefaultBinary).
1540                let candidate_dir_opt = match &target.origin {
1541                    Some(TargetOrigin::SingleFile(path))
1542                    | Some(TargetOrigin::DefaultBinary(path)) => path.parent(),
1543                    _ => None,
1544                };
1545
1546                if let Some(candidate_dir) = candidate_dir_opt {
1547                    if has_tauri_conf(candidate_dir) {
1548                        println!("Using candidate directory: {}", candidate_dir.display());
1549                        self.execution_dir = Some(candidate_dir.to_path_buf());
1550                    } else if let Some(manifest_parent) = target.manifest_path.parent() {
1551                        if has_tauri_conf(manifest_parent) {
1552                            println!("Using manifest parent: {}", manifest_parent.display());
1553                            self.execution_dir = Some(manifest_parent.to_path_buf());
1554                        } else if let Some(grandparent) = manifest_parent.parent() {
1555                            if has_tauri_conf(grandparent) {
1556                                println!("Using manifest grandparent: {}", grandparent.display());
1557                                self.execution_dir = Some(grandparent.to_path_buf());
1558                            } else {
1559                                println!("No tauri.conf.json found in candidate, manifest parent, or grandparent; defaulting to manifest parent: {}", manifest_parent.display());
1560                                self.execution_dir = Some(manifest_parent.to_path_buf());
1561                            }
1562                        } else {
1563                            println!("No grandparent for manifest; defaulting to candidate directory: {}", candidate_dir.display());
1564                            self.execution_dir = Some(candidate_dir.to_path_buf());
1565                        }
1566                    } else {
1567                        println!(
1568                            "No manifest parent found for: {}",
1569                            target.manifest_path.display()
1570                        );
1571                    }
1572                    // Check for package.json and run npm ls if found.
1573                    println!("Checking for package.json in: {}", candidate_dir.display());
1574                    if has_file(candidate_dir, "package.json") {
1575                        crate::e_installer::check_npm_and_install(candidate_dir).ok();
1576                    }
1577                } else if let Some(manifest_parent) = target.manifest_path.parent() {
1578                    if has_tauri_conf(manifest_parent) {
1579                        println!("Using manifest parent: {}", manifest_parent.display());
1580                        self.execution_dir = Some(manifest_parent.to_path_buf());
1581                    } else if let Some(grandparent) = manifest_parent.parent() {
1582                        if has_tauri_conf(grandparent) {
1583                            println!("Using manifest grandparent: {}", grandparent.display());
1584                            self.execution_dir = Some(grandparent.to_path_buf());
1585                        } else {
1586                            println!(
1587                                "No tauri.conf.json found; defaulting to manifest parent: {}",
1588                                manifest_parent.display()
1589                            );
1590                            self.execution_dir = Some(manifest_parent.to_path_buf());
1591                        }
1592                    }
1593                    // Check for package.json and run npm ls if found.
1594                    println!(
1595                        "Checking for package.json in: {}",
1596                        manifest_parent.display()
1597                    );
1598                    if has_file(manifest_parent, "package.json") {
1599                        crate::e_installer::check_npm_and_install(manifest_parent).ok();
1600                    }
1601                    if has_file(Path::new("."), "package.json") {
1602                        crate::e_installer::check_npm_and_install(manifest_parent).ok();
1603                    }
1604                } else {
1605                    println!(
1606                        "No manifest parent found for: {}",
1607                        target.manifest_path.display()
1608                    );
1609                }
1610                self.args.push("tauri".into());
1611                self.args.push("dev".into());
1612            }
1613            TargetKind::ManifestLeptos => {
1614                let readme_path = target
1615                    .manifest_path
1616                    .parent()
1617                    .map(|p| p.join("README.md"))
1618                    .filter(|p| p.exists())
1619                    .or_else(|| {
1620                        target
1621                            .manifest_path
1622                            .parent()
1623                            .map(|p| p.join("readme.md"))
1624                            .filter(|p| p.exists())
1625                    });
1626
1627                if let Some(readme) = readme_path {
1628                    if let Ok(mut file) = std::fs::File::open(&readme) {
1629                        let mut contents = String::new();
1630                        if file.read_to_string(&mut contents).is_ok()
1631                            && contents.contains("cargo leptos watch")
1632                        {
1633                            // Use cargo leptos watch
1634                            println!("Detected 'cargo leptos watch' in {}", readme.display());
1635                            crate::e_installer::ensure_leptos().unwrap_or_else(|_| {
1636                                eprintln!("Error: Unable to ensure leptos installation.");
1637                                PathBuf::new() // Return an empty PathBuf as a fallback
1638                            });
1639                            self.execution_dir =
1640                                target.manifest_path.parent().map(|p| p.to_path_buf());
1641
1642                            self.alternate_cmd = Some("cargo".to_string());
1643                            self.args.push("leptos".into());
1644                            self.args.push("watch".into());
1645                            self = self.with_required_features(&target.manifest_path, target);
1646                            if let Some(exec_dir) = &self.execution_dir {
1647                                if exec_dir.join("package.json").exists() {
1648                                    println!(
1649                                        "Found package.json in execution directory: {}",
1650                                        exec_dir.display()
1651                                    );
1652                                    crate::e_installer::check_npm_and_install(exec_dir).ok();
1653                                }
1654                            }
1655                            return self;
1656                        }
1657                    }
1658                }
1659
1660                // fallback to trunk
1661                let exe_path = match crate::e_installer::ensure_trunk() {
1662                    Ok(p) => p,
1663                    Err(e) => {
1664                        eprintln!("{}", e);
1665                        return self;
1666                    }
1667                };
1668
1669                if let Some(manifest_parent) = target.manifest_path.parent() {
1670                    println!("Manifest path: {}", target.manifest_path.display());
1671                    println!(
1672                        "Execution directory (same as manifest folder): {}",
1673                        manifest_parent.display()
1674                    );
1675                    self.execution_dir = Some(manifest_parent.to_path_buf());
1676                } else {
1677                    println!(
1678                        "No manifest parent found for: {}",
1679                        target.manifest_path.display()
1680                    );
1681                }
1682                if let Some(exec_dir) = &self.execution_dir {
1683                    if exec_dir.join("package.json").exists() {
1684                        println!(
1685                            "Found package.json in execution directory: {}",
1686                            exec_dir.display()
1687                        );
1688                        crate::e_installer::check_npm_and_install(exec_dir).ok();
1689                    }
1690                }
1691                self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
1692                self.args.push("serve".into());
1693                self.args.push("--open".into());
1694                self.args.push("--color".into());
1695                self.args.push("always".into());
1696                self = self.with_required_features(&target.manifest_path, target);
1697            }
1698            TargetKind::ManifestDioxus => {
1699                // For Dioxus targets, print the manifest path and set the execution directory
1700                let exe_path = match crate::e_installer::ensure_dx() {
1701                    Ok(path) => path,
1702                    Err(e) => {
1703                        eprintln!("Error locating `dx`: {}", e);
1704                        return self;
1705                    }
1706                };
1707                // to be the same directory as the manifest.
1708                if let Some(manifest_parent) = target.manifest_path.parent() {
1709                    println!("Manifest path: {}", target.manifest_path.display());
1710                    println!(
1711                        "Execution directory (same as manifest folder): {}",
1712                        manifest_parent.display()
1713                    );
1714                    self.execution_dir = Some(manifest_parent.to_path_buf());
1715                } else {
1716                    println!(
1717                        "No manifest parent found for: {}",
1718                        target.manifest_path.display()
1719                    );
1720                }
1721                self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
1722                self.args.push("serve".into());
1723                self = self.with_required_features(&target.manifest_path, target);
1724            }
1725            TargetKind::ManifestDioxusExample => {
1726                let exe_path = match crate::e_installer::ensure_dx() {
1727                    Ok(path) => path,
1728                    Err(e) => {
1729                        eprintln!("Error locating `dx`: {}", e);
1730                        return self;
1731                    }
1732                };
1733                // For Dioxus targets, print the manifest path and set the execution directory
1734                // to be the same directory as the manifest.
1735                if let Some(manifest_parent) = target.manifest_path.parent() {
1736                    println!("Manifest path: {}", target.manifest_path.display());
1737                    println!(
1738                        "Execution directory (same as manifest folder): {}",
1739                        manifest_parent.display()
1740                    );
1741                    self.execution_dir = Some(manifest_parent.to_path_buf());
1742                } else {
1743                    println!(
1744                        "No manifest parent found for: {}",
1745                        target.manifest_path.display()
1746                    );
1747                }
1748                self.alternate_cmd = Some(exe_path.as_os_str().to_string_lossy().to_string());
1749                self.args.push("serve".into());
1750                self.args.push("--example".into());
1751                self.args.push(target.name.clone());
1752                self = self.with_required_features(&target.manifest_path, target);
1753            }
1754        }
1755        self
1756    }
1757
1758    /// Configure the command using CLI options.
1759    pub fn with_cli(mut self, cli: &crate::Cli) -> Self {
1760        if cli.quiet && !self.suppressed_flags.contains("quiet") {
1761            // Insert --quiet right after "run" if present.
1762            if let Some(pos) = self.args.iter().position(|arg| arg == &self.subcommand) {
1763                self.args.insert(pos + 1, "--quiet".into());
1764            } else {
1765                self.args.push("--quiet".into());
1766            }
1767        }
1768        if cli.release {
1769            // Insert --release right after the initial "run" command if applicable.
1770            // For example, if the command already contains "run", insert "--release" after it.
1771            if let Some(pos) = self.args.iter().position(|arg| arg == &self.subcommand) {
1772                self.args.insert(pos + 1, "--release".into());
1773            } else {
1774                // If not running a "run" command (like in the Tauri case), simply push it.
1775                self.args.push("--release".into());
1776            }
1777        }
1778        // Append extra arguments (if any) after a "--" separator.
1779        if !cli.extra.is_empty() {
1780            self.args.push("--".into());
1781            self.args.extend(cli.extra.iter().cloned());
1782        }
1783        self
1784    }
1785    /// Append required features based on the manifest, target kind, and name.
1786    /// This method queries your manifest helper function and, if features are found,
1787    /// appends "--features" and the feature list.
1788    pub fn with_required_features(mut self, manifest: &PathBuf, target: &CargoTarget) -> Self {
1789        if !self.args.contains(&"--features".to_string()) {
1790            if let Some(features) = crate::e_manifest::get_required_features_from_manifest(
1791                manifest,
1792                &target.kind,
1793                &target.name,
1794            ) {
1795                self.args.push("--features".to_string());
1796                self.args.push(features);
1797            }
1798        }
1799        self
1800    }
1801
1802    /// Appends extra arguments to the command.
1803    pub fn with_extra_args(mut self, extra: &[String]) -> Self {
1804        if !extra.is_empty() {
1805            // Use "--" to separate Cargo arguments from target-specific arguments.
1806            self.args.push("--".into());
1807            self.args.extend(extra.iter().cloned());
1808        }
1809        self
1810    }
1811
1812    /// Builds the final vector of command-line arguments.
1813    pub fn build(self) -> Vec<String> {
1814        self.args
1815    }
1816
1817    pub fn is_compiler_target(&self) -> bool {
1818        let supported_subcommands = ["run", "build", "check", "leptos", "tauri"];
1819        if let Some(alternate) = &self.alternate_cmd {
1820            if alternate == "trunk" {
1821                return true;
1822            }
1823            if alternate != "cargo" {
1824                return false;
1825            }
1826        }
1827        if let Some(_) = self
1828            .args
1829            .iter()
1830            .position(|arg| supported_subcommands.contains(&arg.as_str()))
1831        {
1832            return true;
1833        }
1834        false
1835    }
1836
1837    pub fn injected_args(&self) -> (String, Vec<String>) {
1838        let mut new_args = self.args.clone();
1839        let supported_subcommands = [
1840            "run", "build", "test", "bench", "clean", "doc", "publish", "update",
1841        ];
1842
1843        if self.is_filter {
1844            if let Some(pos) = new_args
1845                .iter()
1846                .position(|arg| supported_subcommands.contains(&arg.as_str()))
1847            {
1848                // If the command is a supported subcommand like "cargo run", insert the JSON output format and color options.
1849                new_args.insert(pos + 1, "--message-format=json".into());
1850                new_args.insert(pos + 2, "--color".into());
1851                new_args.insert(pos + 3, "always".into());
1852            }
1853        }
1854
1855        let program = self.alternate_cmd.as_deref().unwrap_or("cargo").to_string();
1856        (program, new_args)
1857    }
1858
1859    pub fn print_command(&self) {
1860        let (program, new_args) = self.injected_args();
1861        println!("{} {}", program, new_args.join(" "));
1862    }
1863
1864    /// builds a std::process::Command.
1865    pub fn build_command(&self) -> Command {
1866        let (program, new_args) = self.injected_args();
1867
1868        let mut cmd = Command::new(program);
1869        cmd.args(new_args);
1870
1871        if let Some(dir) = &self.execution_dir {
1872            cmd.current_dir(dir);
1873        }
1874
1875        cmd
1876    }
1877    /// Runs the command and returns everything it printed (stdout + stderr),
1878    /// regardless of exit status.
1879    pub fn capture_output(&self) -> anyhow::Result<String> {
1880        // Build and run
1881        let mut cmd = self.build_command();
1882        let output = cmd
1883            .output()
1884            .map_err(|e| anyhow::anyhow!("Failed to spawn cargo process: {}", e))?;
1885
1886        // Decode both stdout and stderr lossily
1887        let mut all = String::new();
1888        all.push_str(&String::from_utf8_lossy(&output.stdout));
1889        all.push_str(&String::from_utf8_lossy(&output.stderr));
1890
1891        // Return the combined string, even if exit was !success
1892        Ok(all)
1893    }
1894}
1895
1896fn show_graphical_panic(
1897    line: &str,
1898    prior_response: Option<CallbackResponse>,
1899    manifest_path: &PathBuf,
1900) {
1901    if let Ok(e_window_path) = which("e_window") {
1902        // Compose a nice message for e_window's stdin
1903        // let stats = stats.lock().unwrap();
1904        // Compose a table with cargo-e and its version, plus panic info
1905        let cargo_e_version = env!("CARGO_PKG_VERSION");
1906
1907        let anchor: String = {
1908            // If there's no prior response, return an empty string.
1909            if prior_response.is_none() {
1910                String::new()
1911            } else {
1912                // Try to parse the line as "file:line:col"
1913                let prior = prior_response.as_ref().unwrap();
1914                let file = prior.file.as_deref().unwrap_or("");
1915                let line_num = prior.line.map(|n| n.to_string()).unwrap_or_default();
1916                let col_num = prior.column.map(|n| n.to_string()).unwrap_or_default();
1917
1918                let full_path = std::fs::canonicalize(file).unwrap_or_else(|_| {
1919                    // Remove the top folder from the file path if possible
1920                    let stripped_file = Path::new(file).components().skip(1).collect::<PathBuf>();
1921                    let fallback_path = stripped_file.clone();
1922                    std::fs::canonicalize(&fallback_path).unwrap_or_else(|_| {
1923                        let manifest_dir = manifest_path.parent().unwrap_or_else(|| {
1924                            eprintln!(
1925                                "Failed to determine parent directory for manifest: {:?}",
1926                                manifest_path
1927                            );
1928                            Path::new(".")
1929                        });
1930                        let parent_fallback_path = manifest_dir.join(file);
1931                        std::fs::canonicalize(&parent_fallback_path).unwrap_or_else(|_| {
1932                            eprintln!("Failed to resolve full path for: {} using ../", file);
1933                            let parent_fallback_path = manifest_dir.join(&stripped_file);
1934                            if parent_fallback_path.exists() {
1935                                parent_fallback_path
1936                            } else {
1937                                PathBuf::from(file)
1938                            }
1939                        })
1940                    })
1941                });
1942                let stripped_file = full_path.to_string_lossy().replace("\\\\?\\", "");
1943                let code_path = which("code").unwrap_or_else(|_| "code".to_string().into());
1944                String::from(format!(
1945                    "\nanchor: code {} {} {}|\"{}\" --goto \"{}:{}:{}\"\n",
1946                    stripped_file,
1947                    prior.line.unwrap_or(0),
1948                    prior.column.unwrap_or(0),
1949                    code_path.display(),
1950                    stripped_file,
1951                    prior.line.unwrap_or(0),
1952                    prior.column.unwrap_or(0)
1953                ))
1954            }
1955        };
1956        let context = ThreadLocalContext::get_context();
1957        let mut card = format!(
1958            "--title \"panic: {target}\" --width 400 --height 300\n\
1959                        target | {target} | string\n\
1960                        cargo-e | {version} | string\n\
1961                        \n\
1962                        panic: {target}\n{line}",
1963            target = context.target_name,
1964            version = cargo_e_version,
1965            line = line
1966        );
1967        if let Some(prior) = prior_response {
1968            if let Some(msg) = &prior.message {
1969                card = format!("{}\n{}", card, msg);
1970            }
1971        }
1972        if !anchor.is_empty() {
1973            card = format!("{}{}", card, anchor);
1974        }
1975        let child = std::process::Command::new(e_window_path)
1976            .stdin(std::process::Stdio::piped())
1977            .spawn();
1978        if let Ok(mut child) = child {
1979            if let Some(stdin) = child.stdin.as_mut() {
1980                use std::io::Write;
1981                let _ = stdin.write_all(card.as_bytes());
1982            }
1983        }
1984    }
1985}
1986/// Resolves a file path by:
1987///   1. If the path is relative, try to resolve it relative to the current working directory.
1988///   2. If that file does not exist, try to resolve it relative to the parent directory of the manifest path.
1989///   3. Otherwise, return the original relative path.
1990pub(crate) fn resolve_file_path(manifest_path: &PathBuf, file_str: &str) -> PathBuf {
1991    let file_path = Path::new(file_str);
1992    if file_path.is_relative() {
1993        // 1. Try resolving relative to the current working directory.
1994        if let Ok(cwd) = env::current_dir() {
1995            let cwd_path = cwd.join(file_path);
1996            if cwd_path.exists() {
1997                return cwd_path;
1998            }
1999        }
2000        // 2. Try resolving relative to the parent of the manifest path.
2001        if let Some(manifest_parent) = manifest_path.parent() {
2002            let parent_path = manifest_parent.join(file_path);
2003            if parent_path.exists() {
2004                return parent_path;
2005            }
2006        }
2007        // 3. Neither existed; return the relative path as-is.
2008        return file_path.to_path_buf();
2009    }
2010    file_path.to_path_buf()
2011}
2012
2013// --- Example usage ---
2014#[cfg(test)]
2015mod tests {
2016    use crate::e_target::TargetOrigin;
2017
2018    use super::*;
2019
2020    #[test]
2021    fn test_command_builder_example() {
2022        let target_name = "my_example".to_string();
2023        let target = CargoTarget {
2024            name: "my_example".to_string(),
2025            display_name: "My Example".to_string(),
2026            manifest_path: "Cargo.toml".into(),
2027            kind: TargetKind::Example,
2028            extended: true,
2029            toml_specified: false,
2030            origin: Some(TargetOrigin::SingleFile(PathBuf::from(
2031                "examples/my_example.rs",
2032            ))),
2033        };
2034
2035        let extra_args = vec!["--flag".to_string(), "value".to_string()];
2036
2037        let manifest_path = PathBuf::from("Cargo.toml");
2038        let args = CargoCommandBuilder::new(&target_name, &manifest_path, "run", false)
2039            .with_target(&target)
2040            .with_extra_args(&extra_args)
2041            .build();
2042
2043        // For an example target, we expect something like:
2044        // cargo run --example my_example --manifest-path Cargo.toml -- --flag value
2045        assert!(args.contains(&"--example".to_string()));
2046        assert!(args.contains(&"my_example".to_string()));
2047        assert!(args.contains(&"--manifest-path".to_string()));
2048        assert!(args.contains(&"Cargo.toml".to_string()));
2049        assert!(args.contains(&"--".to_string()));
2050        assert!(args.contains(&"--flag".to_string()));
2051        assert!(args.contains(&"value".to_string()));
2052    }
2053}