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