cargo_e/
e_command_builder.rs

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