cargo_e/
e_command_builder.rs

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