cargo_e/
e_command_builder.rs

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