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