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