cargo_e/
e_cargocommand_ext.rs

1use crate::e_command_builder::{CargoCommandBuilder, TerminalError};
2use crate::e_eventdispatcher::EventDispatcher;
3#[allow(unused_imports)]
4use cargo_metadata::Message;
5use nu_ansi_term::{Color, Style};
6#[cfg(feature = "uses_serde")]
7use serde_json;
8use std::collections::VecDeque;
9use std::io::{BufRead, BufReader, Read};
10use std::process::ExitStatus;
11use std::process::{Child, Command, Stdio};
12#[allow(unused_imports)]
13use std::sync::atomic::{AtomicUsize, Ordering};
14use std::sync::{Arc, Mutex};
15use std::time::{Duration, SystemTime};
16use std::{fmt, thread};
17// enum CaptureMode {
18//     Filtering(DispatcherSet),
19//     Passthrough { stdout: std::io::Stdout, stderr: std::io::Stderr },
20// }
21// struct DispatcherSet {
22//     stdout: Option<Arc<EventDispatcher>>,
23//     stderr: Option<Arc<EventDispatcher>>,
24//     progress: Option<Arc<EventDispatcher>>,
25//     stage: Option<Arc<EventDispatcher>>,
26// }
27
28/// CargoStats tracks counts for different cargo events and also stores the first occurrence times.
29#[derive(Debug, Default, Clone)]
30pub struct CargoStats {
31    pub is_comiler_target: bool,
32    pub start_time: Option<SystemTime>,
33    pub compiler_message_count: usize,
34    pub compiler_artifact_count: usize,
35    pub build_script_executed_count: usize,
36    pub build_finished_count: usize,
37    // Record the first occurrence of each stage.
38    pub compiler_message_time: Option<SystemTime>,
39    pub compiler_artifact_time: Option<SystemTime>,
40    pub build_script_executed_time: Option<SystemTime>,
41    pub build_finished_time: Option<SystemTime>,
42}
43
44#[derive(Clone)]
45pub struct CargoDiagnostic {
46    pub lineref: String,
47    pub level: String,
48    pub message: String,
49    pub error_code: Option<String>,
50    pub suggestion: Option<String>,
51    pub note: Option<String>,
52    pub help: Option<String>,
53    pub uses_color: bool,
54    pub diag_number: Option<usize>,
55    pub diag_num_padding: Option<usize>,
56}
57
58impl CargoDiagnostic {
59    pub fn print_short(&self) {
60        // Render the full Debug output
61        let full = format!("{:?}", self);
62        // Grab only the first line (or an empty string if somehow there isn't one)
63        let first = full.lines().next().unwrap_or("");
64        println!("{}", first);
65    }
66}
67impl CargoDiagnostic {
68    #[allow(clippy::too_many_arguments)]
69    pub fn new(
70        lineref: String,
71        level: String,
72        message: String,
73        error_code: Option<String>,
74        suggestion: Option<String>,
75        note: Option<String>,
76        help: Option<String>,
77        uses_color: bool,
78        diag_number: Option<usize>,
79        diag_num_padding: Option<usize>,
80    ) -> Self {
81        CargoDiagnostic {
82            lineref,
83            level,
84            message,
85            error_code,
86            suggestion,
87            note,
88            help,
89            uses_color,
90            diag_number,
91            diag_num_padding,
92        }
93    }
94
95    fn update_suggestion_with_lineno(
96        &self,
97        suggestion: &str,
98        file: String,
99        line_number: usize,
100    ) -> String {
101        // Regex to match line number in the suggestion (e.g., "79 | fn clean<S: AsRef<str>>(s: S) -> String {")
102        let suggestion_regex = regex::Regex::new(r"(?P<line>\d+)\s*\|\s*(.*)").unwrap();
103
104        // Iterate through suggestion lines and check line numbers
105        suggestion
106            .lines()
107            .filter_map(|line| {
108                let binding = line.replace(['|', '^'], "");
109                let cleaned_line = binding.trim();
110
111                // If the line becomes empty after removing | and ^, skip it
112                if cleaned_line.is_empty() {
113                    return None; // Skip empty lines
114                }
115                if let Some(caps) = suggestion_regex.captures(line.trim()) {
116                    let suggestion_line: usize = caps["line"].parse().unwrap_or(line_number); // If parsing fails, use the default line number
117                                                                                              // Replace the line number if it doesn't match the diagnostic's line number
118                    if suggestion_line != line_number {
119                        return Some(format!(
120                            "{}:{} | {}",
121                            file,
122                            suggestion_line, // Replace with the actual diagnostic line number
123                            caps.get(2).map_or("", |m| m.as_str())
124                        ));
125                    } else {
126                        // If the line number matches, return the original suggestion line without line number
127                        return Some(
128                            caps.get(2)
129                                .map_or("".to_owned(), |m| m.as_str().to_string()),
130                        );
131                    }
132                }
133                Some(line.to_string())
134            })
135            .collect::<Vec<String>>()
136            .join("\n")
137    }
138}
139
140impl fmt::Debug for CargoDiagnostic {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        // Capitalize the first letter of the level
143        let level_cap = self
144            .level
145            .chars()
146            .next()
147            .unwrap_or(' ')
148            .to_uppercase()
149            .to_string();
150
151        // Extract the file and line number from lineref (e.g., "cargo-e\src\e_command_builder.rs:79:8")
152        let lineref_regex = regex::Regex::new(r"(?P<file>.*):(?P<line>\d+):(?P<col>\d+)").unwrap();
153        let lineref_caps = lineref_regex.captures(&self.lineref);
154
155        let file = lineref_caps
156            .as_ref()
157            .and_then(|caps| caps.name("file").map(|m| m.as_str().to_string()))
158            .unwrap_or_else(|| "unknown file".to_string());
159        let line_number: usize = lineref_caps
160            .as_ref()
161            .and_then(|caps| caps.name("line").map(|m| m.as_str().parse().unwrap_or(0)))
162            .unwrap_or(0);
163
164        // Format the diagnostic number with padding
165        let padded_diag_number = if let Some(dn) = &self.diag_number {
166            format!("{:0width$}", dn, width = self.diag_num_padding.unwrap_or(0))
167        } else {
168            String::new()
169        };
170
171        // Combine the level and diagnostic number
172        let diag_number = format!("{}{}:", level_cap, padded_diag_number);
173
174        // Color the diagnostic number if color is enabled
175        let diag_number_colored = if self.uses_color {
176            match self.level.as_str() {
177                "warning" => Color::Yellow.paint(&diag_number).to_string(),
178                "error" => Color::Red.paint(&diag_number).to_string(),
179                "help" => Color::Purple.paint(&diag_number).to_string(),
180                _ => Color::Green.paint(&diag_number).to_string(),
181            }
182        } else {
183            diag_number.clone()
184        };
185
186        // Print the diagnostic number and lineref
187        if self.uses_color {
188            write!(f, "{} ", diag_number_colored)?;
189            let underlined_text = Style::new().underline().paint(&self.lineref).to_string();
190            write!(f, "\x1b[4m{}\x1b[0m ", underlined_text)?; // Apply underline using ANSI codes
191        } else {
192            write!(f, "{} ", diag_number)?;
193            write!(f, "{} ", self.lineref)?; // Plain lineref without color
194        }
195
196        // Print the message
197        write!(f, "{}: ", self.message)?;
198
199        // Print the suggestion if present
200        if let Some(suggestion) = &self.suggestion {
201            let suggestion = self.update_suggestion_with_lineno(suggestion, file, line_number);
202            let suggestion_text = if self.uses_color {
203                Color::Green.paint(suggestion).to_string()
204            } else {
205                suggestion.clone()
206            };
207            write!(f, "{} ", suggestion_text)?;
208        }
209
210        // Print the note if present
211        if let Some(note) = &self.note {
212            let note_text = if self.uses_color {
213                Color::Blue.paint(note).to_string()
214            } else {
215                note.clone()
216            };
217            write!(f, "\n{}", note_text)?;
218        }
219
220        // Print the help if present
221        if let Some(help) = &self.help {
222            let help_text = if self.uses_color {
223                Color::LightYellow.paint(help).to_string()
224            } else {
225                help.clone()
226            };
227            write!(f, "\n{} ", help_text)?;
228        }
229
230        // Finish the debug formatting
231        write!(f, "") // No further fields are needed
232    }
233}
234
235/// CargoProcessResult is returned when the cargo process completes.
236#[derive(Debug, Default, Clone)]
237pub struct CargoProcessResult {
238    pub target_name: String,
239    pub cmd: String,
240    pub args: Vec<String>,
241    pub pid: u32,
242    pub terminal_error: Option<TerminalError>,
243    pub exit_status: Option<ExitStatus>,
244    pub start_time: Option<SystemTime>,
245    pub build_finished_time: Option<SystemTime>,
246    pub end_time: Option<SystemTime>,
247    pub build_elapsed: Option<Duration>,
248    pub runtime_elapsed: Option<Duration>,
249    pub elapsed_time: Option<Duration>,
250    pub stats: CargoStats,
251    pub build_output_size: usize,
252    pub runtime_output_size: usize,
253    pub diagnostics: Vec<CargoDiagnostic>,
254    pub is_filter: bool,
255}
256
257impl CargoProcessResult {
258    /// Print every diagnostic in full detail.
259    pub fn print_exact(&self) {
260        if self.diagnostics.is_empty() {
261            return;
262        }
263        println!(
264            "--- Full Diagnostics for PID {} --- {}",
265            self.pid,
266            self.diagnostics.len()
267        );
268        for diag in &self.diagnostics {
269            println!("{:?}", diag);
270        }
271    }
272
273    /// Print warnings first, then errors, one‐line summary.
274    pub fn print_short(&self) {
275        if self.diagnostics.is_empty() {
276            return;
277        }
278        // let warnings: Vec<_> = self.diagnostics.iter()
279        //     .filter(|d| d.level.eq("warning"))
280        //     .collect();
281        let errors: Vec<_> = self
282            .diagnostics
283            .iter()
284            .filter(|d| d.level.eq("error"))
285            .collect();
286
287        // println!("--- Warnings ({} total) ---", warnings.len());
288        // for d in warnings {
289        //     d.print_short();
290        // }
291
292        println!("--- Errors ({} total) ---", errors.len());
293        for d in errors {
294            d.print_short();
295        }
296    }
297
298    /// Print a compact, zero‑padded, numbered list of *all* diagnostics.
299    pub fn print_compact(&self) {
300        if self.diagnostics.is_empty() {
301            return;
302        }
303        let total = self.diagnostics.len();
304        println!("--- All Diagnostics ({} total) ---", total);
305        for diag in self.diagnostics.iter() {
306            println!(
307                "{} {}: {} {}",
308                diag.level,
309                diag.diag_number.unwrap_or_default(),
310                diag.lineref,
311                diag.message.trim()
312            );
313        }
314    }
315}
316
317/// CargoProcessHandle holds the cargo process and related state.
318#[derive(Debug)]
319pub struct CargoProcessHandle {
320    pub child: Child,
321    pub result: CargoProcessResult,
322    pub pid: u32,
323    pub requested_exit: bool,
324    pub stdout_handle: thread::JoinHandle<()>,
325    pub stderr_handle: thread::JoinHandle<()>,
326    pub start_time: SystemTime,
327    pub stats: Arc<Mutex<CargoStats>>,
328    pub stdout_dispatcher: Option<Arc<EventDispatcher>>,
329    pub stderr_dispatcher: Option<Arc<EventDispatcher>>,
330    pub progress_dispatcher: Option<Arc<EventDispatcher>>,
331    pub stage_dispatcher: Option<Arc<EventDispatcher>>,
332    pub estimate_bytes: Option<usize>,
333    // Separate progress counters for build and runtime output.
334    pub build_progress_counter: Arc<AtomicUsize>,
335    pub runtime_progress_counter: Arc<AtomicUsize>,
336    pub terminal_error_flag: Arc<Mutex<TerminalError>>,
337    pub diagnostics: Arc<Mutex<Vec<CargoDiagnostic>>>,
338    pub is_filter: bool,
339}
340
341impl CargoProcessHandle {
342    pub fn print_results(result: &CargoProcessResult) {
343        let start_time = result.start_time.unwrap_or(SystemTime::now());
344        println!("-------------------------------------------------");
345        println!("Process started at: {:?}", result.start_time);
346        if let Some(build_time) = result.build_finished_time {
347            println!("Build phase ended at: {:?}", build_time);
348            println!(
349                "Build phase elapsed:  {}",
350                crate::e_fmt::format_duration(
351                    build_time
352                        .duration_since(start_time)
353                        .unwrap_or_else(|_| Duration::new(0, 0))
354                )
355            );
356        } else {
357            println!("No BuildFinished timestamp recorded.");
358        }
359        println!("Process ended at:   {:?}", result.end_time);
360        if let Some(runtime_dur) = result.runtime_elapsed {
361            println!(
362                "Runtime phase elapsed: {}",
363                crate::e_fmt::format_duration(runtime_dur)
364            );
365        }
366        if let Some(build_dur) = result.build_elapsed {
367            println!(
368                "Build phase elapsed:   {}",
369                crate::e_fmt::format_duration(build_dur)
370            );
371        }
372        if let Some(total_elapsed) = result
373            .end_time
374            .and_then(|end| end.duration_since(start_time).ok())
375        {
376            println!(
377                "Total elapsed time:   {}",
378                crate::e_fmt::format_duration(total_elapsed)
379            );
380        } else {
381            println!("No total elapsed time available.");
382        }
383        println!(
384            "Build output size:  {} ({} bytes)",
385            crate::e_fmt::format_bytes(result.build_output_size),
386            result.build_output_size
387        );
388        println!(
389            "Runtime output size: {} ({} bytes)",
390            crate::e_fmt::format_bytes(result.runtime_output_size),
391            result.runtime_output_size
392        );
393        println!("-------------------------------------------------");
394    }
395
396    /// Kill the cargo process if needed.
397    pub fn kill(&mut self) -> std::io::Result<()> {
398        self.child.kill()
399    }
400    pub fn pid(&self) -> u32 {
401        self.pid
402    }
403
404    //     pub fn wait(&mut self) -> std::io::Result<CargoProcessResult> {
405    //     // Lock the instance since `self` is an `Arc`
406    //     // let mut cargo_process_handle = self.lock().unwrap();  // `lock()` returns a mutable reference
407
408    //     // Call wait on the child process
409    //     let status = self.child.wait()?;  // Call wait on the child process
410
411    //     println!("Cargo process finished with status: {:?}", status);
412
413    //     let end_time = SystemTime::now();
414
415    //     // Retrieve the statistics from the process handle
416    //     let stats = Arc::try_unwrap(self.stats.clone())
417    //         .map(|mutex| mutex.into_inner().unwrap())
418    //         .unwrap_or_else(|arc| (*arc.lock().unwrap()).clone());
419
420    //     let build_out = self.build_progress_counter.load(Ordering::Relaxed);
421    //     let runtime_out = self.runtime_progress_counter.load(Ordering::Relaxed);
422
423    //     // Calculate phase durations if build_finished_time is recorded
424    //     let (build_elapsed, runtime_elapsed) = if let Some(build_finished) = stats.build_finished_time {
425    //         let build_dur = build_finished.duration_since(self.start_time)
426    //             .unwrap_or_else(|_| Duration::new(0, 0));
427    //         let runtime_dur = end_time.duration_since(build_finished)
428    //             .unwrap_or_else(|_| Duration::new(0, 0));
429    //         (Some(build_dur), Some(runtime_dur))
430    //     } else {
431    //         (None, None)
432    //     };
433
434    //     self.result.exit_status = Some(status);
435    //     self.result.end_time = Some(end_time);
436    //     self.result.build_output_size = self.build_progress_counter.load(Ordering::Relaxed);
437    //     self.result.runtime_output_size = self.runtime_progress_counter.load(Ordering::Relaxed);
438
439    //     Ok(self.result.clone())
440    //     // Return the final process result
441    //     // Ok(CargoProcessResult {
442    //     //     pid: self.pid,
443    //     //     exit_status: Some(status),
444    //     //     start_time: Some(self.start_time),
445    //     //     build_finished_time: stats.build_finished_time,
446    //     //     end_time: Some(end_time),
447    //     //     build_elapsed,
448    //     //     runtime_elapsed,
449    //     //     stats,
450    //     //     build_output_size: build_out,
451    //     //     runtime_output_size: runtime_out,
452    //     // })
453    // }
454
455    //  pub fn wait(self: Arc<Self>) -> std::io::Result<CargoProcessResult> {
456    //     let mut global = GLOBAL_CHILDREN.lock().unwrap();
457
458    //     // Lock and access the CargoProcessHandle inside the Mutex
459    //     if let Some(cargo_process_handle) = global.iter_mut().find(|handle| {
460    //         handle.lock().unwrap().pid == self.pid  // Compare the pid to find the correct handle
461    //     }) {
462    //         let mut cargo_process_handle = cargo_process_handle.lock().unwrap();  // Mutably borrow the process handle
463
464    //         let status = cargo_process_handle.child.wait()?;  // Call wait on the child process
465
466    //         println!("Cargo process finished with status: {:?}", status);
467
468    //         let end_time = SystemTime::now();
469
470    //         // Retrieve the statistics from the process handle
471    //         let stats = Arc::try_unwrap(cargo_process_handle.stats.clone())
472    //             .map(|mutex| mutex.into_inner().unwrap())
473    //             .unwrap_or_else(|arc| (*arc.lock().unwrap()).clone());
474
475    //         let build_out = cargo_process_handle.build_progress_counter.load(Ordering::Relaxed);
476    //         let runtime_out = cargo_process_handle.runtime_progress_counter.load(Ordering::Relaxed);
477
478    //         // Calculate phase durations if build_finished_time is recorded
479    //         let (build_elapsed, runtime_elapsed) = if let Some(build_finished) = stats.build_finished_time {
480    //             let build_dur = build_finished.duration_since(cargo_process_handle.start_time)
481    //                 .unwrap_or_else(|_| Duration::new(0, 0));
482    //             let runtime_dur = end_time.duration_since(build_finished)
483    //                 .unwrap_or_else(|_| Duration::new(0, 0));
484    //             (Some(build_dur), Some(runtime_dur))
485    //         } else {
486    //             (None, None)
487    //         };
488
489    //         // Return the final process result
490    //         Ok(CargoProcessResult {
491    //             exit_status: status,
492    //             start_time: cargo_process_handle.start_time,
493    //             build_finished_time: stats.build_finished_time,
494    //             end_time,
495    //             build_elapsed,
496    //             runtime_elapsed,
497    //             stats,
498    //             build_output_size: build_out,
499    //             runtime_output_size: runtime_out,
500    //         })
501    //     } else {
502    //         Err(std::io::Error::new(std::io::ErrorKind::NotFound, "Process handle not found").into())
503    //     }
504    // }
505
506    // Wait for the process and output threads to finish.
507    // Computes elapsed times for the build phase and runtime phase, and returns a CargoProcessResult.
508    // pub fn wait(mut self) -> std::io::Result<CargoProcessResult> {
509    //     let status = self.child.wait()?;
510    //     println!("Cargo process finished with status: {:?}", status);
511
512    //     self.stdout_handle.join().expect("stdout thread panicked");
513    //     self.stderr_handle.join().expect("stderr thread panicked");
514
515    //     let end_time = SystemTime::now();
516
517    //     // Retrieve the statistics.
518    //     let stats = Arc::try_unwrap(self.stats)
519    //         .map(|mutex| mutex.into_inner().unwrap())
520    //         .unwrap_or_else(|arc| (*arc.lock().unwrap()).clone());
521
522    //     let build_out = self.build_progress_counter.load(Ordering::Relaxed);
523    //     let runtime_out = self.runtime_progress_counter.load(Ordering::Relaxed);
524
525    //     // Calculate phase durations if build_finished_time is recorded.
526    //     let (build_elapsed, runtime_elapsed) = if let Some(build_finished) = stats.build_finished_time {
527    //         let build_dur = build_finished.duration_since(self.start_time).unwrap_or_else(|_| Duration::new(0, 0));
528    //         let runtime_dur = end_time.duration_since(build_finished).unwrap_or_else(|_| Duration::new(0, 0));
529    //         (Some(build_dur), Some(runtime_dur))
530    //     } else {
531    //         (None, None)
532    //     };
533
534    //     Ok(CargoProcessResult {
535    //         exit_status: status,
536    //         start_time: self.start_time,
537    //         build_finished_time: stats.build_finished_time,
538    //         end_time,
539    //         build_elapsed,
540    //         runtime_elapsed,
541    //         stats,
542    //         build_output_size: build_out,
543    //         runtime_output_size: runtime_out,
544    //     })
545    // }
546
547    /// Returns a formatted status string.
548    /// If `system` is provided, CPU/memory and runtime info is displayed on the right.
549    /// Otherwise, only the start time is shown.
550    pub fn format_status(&self, process: Option<&sysinfo::Process>) -> String {
551        // Ensure the start time is available.
552        let start_time = self
553            .result
554            .start_time
555            .expect("start_time should be initialized");
556        let start_dt: chrono::DateTime<chrono::Local> = start_time.into();
557        let start_str = start_dt.format("%H:%M:%S").to_string();
558        // Use ANSI coloring for the left display.
559        let colored_start = nu_ansi_term::Color::Green.paint(&start_str).to_string();
560
561        if let Some(process) = process {
562            // if let Some(process) = system.process((self.pid as usize).into()) {
563            let cpu_usage = process.cpu_usage();
564            let mem_kb = process.memory();
565            let mem_human = if mem_kb >= 1024 {
566                format!("{:.2} MB", mem_kb as f64 / 1024.0)
567            } else {
568                format!("{} KB", mem_kb)
569            };
570
571            let now = SystemTime::now();
572            let runtime_duration = now.duration_since(start_time).unwrap();
573            let runtime_str = crate::e_fmt::format_duration(runtime_duration);
574
575            let left_display = format!(
576                "{} | CPU: {:.2}% | Mem: {}",
577                colored_start, cpu_usage, mem_human
578            );
579            // Use plain text for length calculations.
580            let left_plain = format!(
581                "{} | CPU: {:.2}% | Mem: {}",
582                start_str, cpu_usage, mem_human
583            );
584
585            // Get terminal width.
586            #[cfg(feature = "tui")]
587            let (cols, _) = crossterm::terminal::size().unwrap_or((80, 20));
588            #[cfg(not(feature = "tui"))]
589            let (cols, _) = (80, 20);
590            let total_width = cols as usize;
591
592            // Format the runtime info with underlining.
593            let right_display = nu_ansi_term::Style::new()
594                .reset_before_style()
595                .underline()
596                .paint(&runtime_str)
597                .to_string();
598            let left_len = left_plain.len();
599            let right_len = runtime_str.len();
600            let padding = if total_width > left_len + right_len {
601                total_width - left_len - right_len
602            } else {
603                1
604            };
605
606            let ret = format!("{}{}{}", left_display, " ".repeat(padding), right_display);
607            if ret.trim().is_empty() {
608                String::from("No output available")
609            } else {
610                ret
611            }
612        } else {
613            // return format!("Process {} not found",(self.pid as usize));
614            String::new()
615        }
616        // } else {
617        //     // If system monitoring is disabled, just return the start time.
618        //     colored_start
619        // }
620    }
621}
622
623/// Extension trait to add cargo-specific capture capabilities to Command.
624pub trait CargoCommandExt {
625    fn spawn_cargo_capture(
626        &mut self,
627        builder: Arc<CargoCommandBuilder>,
628        stdout_dispatcher: Option<Arc<EventDispatcher>>,
629        stderr_dispatcher: Option<Arc<EventDispatcher>>,
630        progress_dispatcher: Option<Arc<EventDispatcher>>,
631        stage_dispatcher: Option<Arc<EventDispatcher>>,
632        estimate_bytes: Option<usize>,
633    ) -> CargoProcessHandle;
634    fn spawn_cargo_passthrough(&mut self, builder: Arc<CargoCommandBuilder>) -> CargoProcessHandle;
635}
636
637impl CargoCommandExt for Command {
638    fn spawn_cargo_passthrough(&mut self, builder: Arc<CargoCommandBuilder>) -> CargoProcessHandle {
639        // Spawn the child process without redirecting stdout and stderr
640        let child = self.spawn().unwrap_or_else(|_| {
641            panic!(
642                "Failed to spawn cargo process {:?} {:?}",
643                &builder.alternate_cmd, builder.args
644            )
645        });
646
647        let pid = child.id();
648        let start_time = SystemTime::now();
649        let diagnostics = Arc::clone(&builder.diagnostics);
650        let s = CargoStats {
651            is_comiler_target: builder.is_compiler_target(), // Ensure this field is now valid
652            start_time: Some(start_time),
653            build_finished_time: Some(start_time),
654            ..Default::default()
655        };
656        let stats = Arc::new(Mutex::new(s.clone()));
657        // Try to take ownership of the Vec<CargoDiagnostic> out of the Arc.
658
659        let (cmd, args) = builder.injected_args();
660        // Create the CargoProcessHandle
661        let result = CargoProcessResult {
662            target_name: builder.target_name.clone(),
663            cmd: cmd,
664            args: args,
665            pid,
666            terminal_error: None,
667            exit_status: None,
668            start_time: Some(start_time),
669            build_finished_time: None,
670            end_time: None,
671            elapsed_time: None,
672            build_elapsed: None,
673            runtime_elapsed: None,
674            stats: s, // CargoStats::default(),
675            build_output_size: 0,
676            runtime_output_size: 0,
677            diagnostics: Vec::new(),
678            is_filter: builder.is_filter,
679        };
680
681        // Return the CargoProcessHandle that owns the child process
682        CargoProcessHandle {
683            child,  // The child process is now owned by the handle
684            result, // The result contains information about the process
685            pid,    // The PID of the process
686            stdout_handle: thread::spawn(move || {
687                // This thread is now unnecessary if we are not capturing anything
688                // We can leave it empty or remove it altogether
689            }),
690            stderr_handle: thread::spawn(move || {
691                // This thread is also unnecessary if we are not capturing anything
692            }),
693            start_time,
694            stats,
695            stdout_dispatcher: None,   // No dispatcher is needed
696            stderr_dispatcher: None,   // No dispatcher is needed
697            progress_dispatcher: None, // No dispatcher is needed
698            stage_dispatcher: None,    // No dispatcher is needed
699            estimate_bytes: None,
700            build_progress_counter: Arc::new(AtomicUsize::new(0)),
701            runtime_progress_counter: Arc::new(AtomicUsize::new(0)),
702            requested_exit: false,
703            terminal_error_flag: Arc::new(Mutex::new(TerminalError::NoError)),
704            diagnostics,
705            is_filter: builder.is_filter,
706        }
707    }
708
709    fn spawn_cargo_capture(
710        &mut self,
711        builder: Arc<CargoCommandBuilder>,
712        stdout_dispatcher: Option<Arc<EventDispatcher>>,
713        stderr_dispatcher: Option<Arc<EventDispatcher>>,
714        progress_dispatcher: Option<Arc<EventDispatcher>>,
715        stage_dispatcher: Option<Arc<EventDispatcher>>,
716        estimate_bytes: Option<usize>,
717    ) -> CargoProcessHandle {
718        self.stdout(Stdio::piped()).stderr(Stdio::piped());
719        // println!("Spawning cargo process with capture {:?}",self);
720        let mut child = self.spawn().expect("Failed to spawn cargo process");
721        let pid = child.id();
722        let start_time = SystemTime::now();
723        let diagnostics = Arc::new(Mutex::new(Vec::<CargoDiagnostic>::new()));
724        let s = CargoStats {
725            is_comiler_target: builder.is_compiler_target(),
726            start_time: Some(start_time),
727            ..Default::default()
728        };
729        let stats = Arc::new(Mutex::new(s));
730
731        // Two separate counters: one for build output and one for runtime output.
732        let stderr_compiler_msg = Arc::new(Mutex::new(VecDeque::<String>::new()));
733        let build_progress_counter = Arc::new(AtomicUsize::new(0));
734        let runtime_progress_counter = Arc::new(AtomicUsize::new(0));
735
736        // Clone dispatchers and counters for use in threads.
737        let _stdout_disp_clone = stdout_dispatcher.clone();
738        let progress_disp_clone_stdout = progress_dispatcher.clone();
739        let stage_disp_clone = stage_dispatcher.clone();
740
741        let stats_stdout_clone = Arc::clone(&stats);
742        let stats_stderr_clone = Arc::clone(&stats);
743        let _build_counter_stdout = Arc::clone(&build_progress_counter);
744        let _runtime_counter_stdout = Arc::clone(&runtime_progress_counter);
745
746        // Spawn a thread to process stdout.
747        let _stderr_compiler_msg_clone = Arc::clone(&stderr_compiler_msg);
748        let stdout = child.stdout.take().expect("Failed to capture stdout");
749        // println!("{}: Capturing stdout", pid);
750        let stdout_handle = thread::spawn(move || {
751            let stdout_reader = BufReader::new(stdout);
752            // This flag marks whether we are still in the build phase.
753            #[allow(unused_mut)]
754            let mut _in_build_phase = true;
755            let stdout_buffer = Arc::new(Mutex::new(Vec::<String>::new()));
756            let buf = Arc::clone(&stdout_buffer);
757            {
758                for line in stdout_reader.lines().map(|line| line) {
759                    if let Ok(line) = line {
760                        // println!("{}: {}", pid, line);
761                        // Try to parse the line as a JSON cargo message.
762
763                        #[cfg(not(feature = "uses_serde"))]
764                        println!("{}", line);
765                        #[cfg(feature = "uses_serde")]
766                        match serde_json::from_str::<Message>(&line) {
767                            Ok(msg) => {
768                                // let msg_str = format!("{:?}", msg);
769                                // if let Some(ref disp) = stdout_disp_clone {
770                                //     disp.dispatch(&msg_str);
771                                // }
772                                // Add message length to the appropriate counter.
773                                // if in_build_phase {
774                                //     build_counter_stdout.fetch_add(msg_str.len(), Ordering::Relaxed);
775                                // } else {
776                                //     runtime_counter_stdout.fetch_add(msg_str.len(), Ordering::Relaxed);
777                                // }
778                                if let Some(total) = estimate_bytes {
779                                    let current = if _in_build_phase {
780                                        _build_counter_stdout.load(Ordering::Relaxed)
781                                    } else {
782                                        _runtime_counter_stdout.load(Ordering::Relaxed)
783                                    };
784                                    let progress = (current as f64 / total as f64) * 100.0;
785                                    if let Some(ref pd) = progress_disp_clone_stdout {
786                                        pd.dispatch(
787                                            &format!("Progress: {:.2}%", progress),
788                                            stats_stdout_clone.clone(),
789                                        );
790                                    }
791                                }
792
793                                let now = SystemTime::now();
794                                // Process known cargo message variants.
795                                match msg {
796                                    Message::BuildFinished(_) => {
797                                        // Mark the end of the build phase.
798                                        if _in_build_phase {
799                                            _in_build_phase = false;
800                                            let mut s = stats_stdout_clone.lock().unwrap();
801                                            s.build_finished_count += 1;
802                                            s.build_finished_time.get_or_insert(now);
803                                            drop(s);
804                                            // self.result.build_finished_time = Some(now);
805                                            if let Some(ref sd) = stage_disp_clone {
806                                                sd.dispatch(
807                                                    &format!(
808                                                        "Stage: BuildFinished occurred at {:?}",
809                                                        now
810                                                    ),
811                                                    stats_stdout_clone.clone(),
812                                                );
813                                            }
814                                            if let Some(ref sd) = stage_disp_clone {
815                                                sd.dispatch(
816                                                    "Stage: Switching to runtime passthrough",
817                                                    stats_stdout_clone.clone(),
818                                                );
819                                            }
820                                        }
821                                    }
822                                    Message::CompilerMessage(msg) => {
823                                        // println!("parsed{}: {:?}", pid, msg);
824                                        let mut s = stats_stdout_clone.lock().unwrap();
825                                        s.compiler_message_count += 1;
826                                        if s.compiler_message_time.is_none() {
827                                            s.compiler_message_time = Some(now);
828                                            drop(s);
829                                            if let Some(ref sd) = stage_disp_clone {
830                                                sd.dispatch(
831                                                    &format!(
832                                                        "Stage: CompilerMessage occurred at {:?}",
833                                                        now
834                                                    ),
835                                                    stats_stdout_clone.clone(),
836                                                );
837                                            }
838                                        }
839                                        let mut msg_vec =
840                                            _stderr_compiler_msg_clone.lock().unwrap();
841                                        msg_vec.push_back(format!(
842                                            "{}\n\n",
843                                            msg.message.rendered.unwrap_or_default()
844                                        ));
845                                        // let mut diags = diagnostics.lock().unwrap();
846                                        // let diag = crate::e_eventdispatcher::convert_message_to_diagnostic(msg, &msg_str);
847                                        // diags.push(diag.clone());
848                                        // if let Some(ref sd) = stage_disp_clone {
849                                        //     sd.dispatch(&format!("Stage: Diagnostic occurred at {:?}", now));
850                                        // }
851                                    }
852                                    Message::CompilerArtifact(_) => {
853                                        let mut s = stats_stdout_clone.lock().unwrap();
854                                        s.compiler_artifact_count += 1;
855                                        if s.compiler_artifact_time.is_none() {
856                                            s.compiler_artifact_time = Some(now);
857                                            drop(s);
858                                            if let Some(ref sd) = stage_disp_clone {
859                                                sd.dispatch(
860                                                    &format!(
861                                                        "Stage: CompilerArtifact occurred at {:?}",
862                                                        now
863                                                    ),
864                                                    stats_stdout_clone.clone(),
865                                                );
866                                            }
867                                        }
868                                    }
869                                    Message::BuildScriptExecuted(_) => {
870                                        let mut s = stats_stdout_clone.lock().unwrap();
871                                        s.build_script_executed_count += 1;
872                                        if s.build_script_executed_time.is_none() {
873                                            s.build_script_executed_time = Some(now);
874                                            drop(s);
875                                            if let Some(ref sd) = stage_disp_clone {
876                                                sd.dispatch(
877                                                    &format!(
878                                                    "Stage: BuildScriptExecuted occurred at {:?}",
879                                                    now
880                                                ),
881                                                    stats_stdout_clone.clone(),
882                                                );
883                                            }
884                                        }
885                                    }
886                                    _ => {}
887                                }
888                            }
889                            Err(_err) => {
890                                // println!("ERROR {} {}: {}",_err, pid, line);
891                                // If JSON parsing fails, assume this is plain runtime output.
892                                // If still in build phase, we assume the build phase has ended.
893                                if _in_build_phase {
894                                    _in_build_phase = false;
895                                    let now = SystemTime::now();
896                                    let mut s = stats_stdout_clone.lock().unwrap();
897                                    s.build_finished_count += 1;
898                                    s.build_finished_time.get_or_insert(now);
899                                    drop(s);
900                                    if let Some(ref sd) = stage_disp_clone {
901                                        sd.dispatch(
902                                            &format!(
903                                                "Stage: BuildFinished (assumed) occurred at {:?}",
904                                                now
905                                            ),
906                                            stats_stdout_clone.clone(),
907                                        );
908                                    }
909                                    buf.lock().unwrap().push(line.to_string());
910                                } else {
911                                    // build is done: first flush anything we buffered
912                                    let mut b = buf.lock().unwrap();
913                                    for l in b.drain(..) {
914                                        println!("{}", l);
915                                    }
916                                    // then print live
917                                    println!("{}", line);
918                                }
919                                if let Some(ref disp) = _stdout_disp_clone {
920                                    disp.dispatch(&line, stats_stdout_clone.clone());
921                                }
922                                // Print the runtime output.
923                                // println!("{}: {}", pid, line);
924                                if line.contains("not a terminal") {
925                                    println!(
926                                        "{}NOT A TERMINAL - MARK AND RUN AGAIN: {}",
927                                        pid, line
928                                    );
929                                }
930                                _runtime_counter_stdout.fetch_add(line.len(), Ordering::Relaxed);
931                                if let Some(total) = estimate_bytes {
932                                    let current = _runtime_counter_stdout.load(Ordering::Relaxed);
933                                    let progress = (current as f64 / total as f64) * 100.0;
934                                    if let Some(ref pd) = progress_disp_clone_stdout {
935                                        pd.dispatch(
936                                            &format!("Progress: {:.2}%", progress),
937                                            stats_stdout_clone.clone(),
938                                        );
939                                    }
940                                }
941                            }
942                        }
943                    }
944                }
945            }
946        }); // End of stdout thread
947
948        let tflag = TerminalError::NoError;
949        // Create a flag to indicate if the process is a terminal process.
950        let terminal_flag = Arc::new(Mutex::new(TerminalError::NoError));
951        // Spawn a thread to capture stderr.
952        let stderr = child.stderr.take().expect("Failed to capture stderr");
953        let stderr_disp_clone = stderr_dispatcher.clone();
954        // let terminal_flag_clone = Arc::clone(&terminal_flag);
955        // let build_counter_stderr = Arc::clone(&build_progress_counter);
956        // let runtime_counter_stderr = Arc::clone(&runtime_progress_counter);
957        // let progress_disp_clone_stderr = progress_dispatcher.clone();
958        let escape_sequence = "\u{1b}[1m\u{1b}[32m";
959        // let diagnostics_clone = Arc::clone(&diagnostics);
960        let stderr_compiler_msg_clone = Arc::clone(&stderr_compiler_msg);
961        // println!("{}: Capturing stderr", pid);
962        let mut stderr_reader = BufReader::new(stderr);
963        let stderr_handle = thread::spawn(move || {
964            //    let mut msg_vec = stderr_compiler_msg_clone.lock().unwrap();
965            loop {
966                // println!("looping stderr thread {}", pid);
967                // Lock the deque and pop all messages available in a while loop
968                while let Some(message) = {
969                    let mut guard = match stderr_compiler_msg_clone.lock() {
970                        Ok(guard) => guard,
971                        Err(err) => {
972                            eprintln!("Failed to lock stderr_compiler_msg_clone: {}", err);
973                            return; // Exit the function or loop in case of an error
974                        }
975                    };
976                    guard.pop_front()
977                } {
978                    for line in message.lines().map(|line| line) {
979                        if let Some(ref disp) = stderr_disp_clone {
980                            // Dispatch the line and receive the Vec<Option<CallbackResponse>>.
981                            let responses = disp.dispatch(line, stats_stderr_clone.clone());
982
983                            // Iterate over the responses.
984                            for ret in responses {
985                                if let Some(response) = ret {
986                                    if response.terminal_status == Some(TerminalError::NoTerminal) {
987                                        // If the response indicates a terminal error, set the flag.
988                                        println!("{} IS A TERMINAL PROCESS - {}", pid, line);
989                                    } else if response.terminal_status
990                                        == Some(TerminalError::NoError)
991                                    {
992                                        // If the response indicates no terminal error, set the flag to NoError.
993                                    } else if response.terminal_status
994                                        == Some(TerminalError::NoTerminal)
995                                    {
996                                        // If the response indicates not a terminal, set the flag to NoTerminal.
997                                        println!("{} IS A TERMINAL PROCESS - {}", pid, line);
998                                    }
999                                    // if let Some(ref msg) = response.message {
1000                                    //     println!("DISPATCH RESULT {} {}", pid, msg);
1001                                    // // }
1002                                    //             let diag = crate::e_eventdispatcher::convert_response_to_diagnostic(response, &line);
1003                                    //             // let mut diags = diagnostics_clone.lock().unwrap();
1004
1005                                    //             let in_multiline = disp.callbacks
1006                                    //             .lock().unwrap()
1007                                    //             .iter()
1008                                    //             .any(|cb| cb.is_reading_multiline.load(Ordering::Relaxed));
1009
1010                                    //         if !in_multiline {
1011                                    //             // start of a new diagnostic
1012                                    //             diags.push(diag);
1013                                    //         } else {
1014                                    //             // continuation → child of the last diagnostic
1015                                    //             if let Some(parent) = diags.last_mut() {
1016                                    //                 parent.children.push(diag);
1017                                    //             } else {
1018                                    //                 // no parent yet (unlikely), just push
1019                                    //                 diags.push(diag);
1020                                    //             }
1021                                    //         }
1022                                }
1023                            }
1024                        }
1025                    }
1026                }
1027                // Sleep briefly if no messages are available to avoid busy waiting
1028                thread::sleep(Duration::from_millis(100));
1029                // If still in build phase, add to the build counter.
1030                // break;
1031
1032                // println!("{}: dave stderr", pid);
1033                // let mut flag = terminal_flag_clone.lock().unwrap();
1034                for line in stderr_reader.by_ref().lines() {
1035                    // println!("SPAWN{}: {:?}", pid, line);
1036                    if let Ok(line) = line {
1037                        // if line.contains("IO(Custom { kind: NotConnected") {
1038                        //     println!("{} IS A TERMINAL PROCESS - {}", pid,line);
1039                        //     continue;
1040                        // }
1041                        let line = if line.starts_with(escape_sequence) {
1042                            // If the line starts with the escape sequence, preserve it and remove leading spaces
1043                            let rest_of_line = &line[escape_sequence.len()..]; // Get the part of the line after the escape sequence
1044                            format!("{}{}", escape_sequence, rest_of_line.trim_start())
1045                        // Reassemble the escape sequence and the trimmed text
1046                        } else {
1047                            line // If it doesn't start with the escape sequence, leave it unchanged
1048                        };
1049                        if let Some(ref disp) = stderr_disp_clone {
1050                            // Dispatch the line and receive the Vec<Option<CallbackResponse>>.
1051                            let responses = disp.dispatch(&line, stats_stderr_clone.clone());
1052                            let mut has_match = false;
1053                            // Iterate over the responses.
1054                            for ret in responses {
1055                                if let Some(_response) = ret {
1056                                    has_match = true;
1057                                    // if response.terminal_status == Some(TerminalError::NoTerminal) {
1058                                    //     // If the response indicates a terminal error, set the flag.
1059                                    //     *flag = TerminalError::NoTerminal;
1060                                    //     println!("{} IS A TERMINAL PROCESS - {}", pid, line);
1061                                    // } else if response.terminal_status == Some(TerminalError::NoError) {
1062                                    //     // If the response indicates no terminal error, set the flag to NoError.
1063                                    //     *flag = TerminalError::NoError;
1064                                    // } else if response.terminal_status == Some(TerminalError::NoTerminal) {
1065                                    //     // If the response indicates not a terminal, set the flag to NoTerminal.
1066                                    //      *flag = TerminalError::NoTerminal;
1067                                    //     println!("{} IS A TERMINAL PROCESS - {}", pid, line);
1068                                    // }
1069                                    // if let Some(ref msg) = response.message {
1070                                    //      println!("DISPATCH RESULT {} {}", pid, msg);
1071                                    // }
1072                                    //     let diag = crate::e_eventdispatcher::convert_response_to_diagnostic(response, &line);
1073                                    //     // let mut diags = diagnostics_clone.lock().unwrap();
1074
1075                                    //     let in_multiline = disp.callbacks
1076                                    //     .lock().unwrap()
1077                                    //     .iter()
1078                                    //     .any(|cb| cb.is_reading_multiline.load(Ordering::Relaxed));
1079
1080                                    // if !in_multiline {
1081                                    //     // start of a new diagnostic
1082                                    //     diags.push(diag);
1083                                    // } else {
1084                                    //     // continuation → child of the last diagnostic
1085                                    //     if let Some(parent) = diags.last_mut() {
1086                                    //         parent.children.push(diag);
1087                                    //     } else {
1088                                    //         // no parent yet (unlikely), just push
1089                                    //         diags.push(diag);
1090                                    //     }
1091                                    // }
1092                                }
1093                            }
1094                            if !has_match && !line.trim().is_empty() && !line.eq("...") {
1095                                // If the line doesn't match any pattern, print it as is.
1096                                println!("{}", line);
1097                            }
1098                        } else {
1099                            println!("ALLLINES {}", line.trim()); //all lines
1100                        }
1101                        // if let Some(ref disp) = stderr_disp_clone {
1102                        //     if let Some(ret) = disp.dispatch(&line) {
1103                        //         if let Some(ref msg) = ret.message {
1104                        //             println!("DISPATCH RESULT {} {}", pid, msg);
1105                        //         }
1106                        //     }
1107                        // }
1108                        // // Here, we assume stderr is less structured. We add its length to runtime counter.
1109                        // runtime_counter_stderr.fetch_add(line.len(), Ordering::Relaxed);
1110                        // if let Some(total) = estimate_bytes {
1111                        //     let current = runtime_counter_stderr.load(Ordering::Relaxed);
1112                        //     let progress = (current as f64 / total as f64) * 100.0;
1113                        //     if let Some(ref pd) = progress_disp_clone_stderr {
1114                        //         pd.dispatch(&format!("Progress: {:.2}%", progress));
1115                        //     }
1116                        // }
1117                    }
1118                }
1119                // println!("{}: dave stderr end", pid);
1120            } //loop
1121        }); // End of stderr thread
1122
1123        let final_diagnostics = {
1124            let diag_lock = diagnostics.lock().unwrap();
1125            diag_lock.clone()
1126        };
1127        let pid = child.id();
1128        let (cmd, args) = builder.injected_args();
1129        let result = CargoProcessResult {
1130            target_name: builder.target_name.clone(),
1131            cmd: cmd,
1132            args: args,
1133            pid,
1134            exit_status: None,
1135            start_time: Some(start_time),
1136            build_finished_time: None,
1137            end_time: None,
1138            elapsed_time: None,
1139            build_elapsed: None,
1140            runtime_elapsed: None,
1141            stats: stats.lock().unwrap().clone(),
1142            build_output_size: 0,
1143            runtime_output_size: 0,
1144            terminal_error: Some(tflag),
1145            diagnostics: final_diagnostics,
1146            is_filter: builder.is_filter,
1147        };
1148        CargoProcessHandle {
1149            child,
1150            result,
1151            pid,
1152            stdout_handle,
1153            stderr_handle,
1154            start_time,
1155            stats,
1156            stdout_dispatcher,
1157            stderr_dispatcher,
1158            progress_dispatcher,
1159            stage_dispatcher,
1160            estimate_bytes,
1161            build_progress_counter,
1162            runtime_progress_counter,
1163            requested_exit: false,
1164            terminal_error_flag: terminal_flag,
1165            diagnostics,
1166            is_filter: builder.is_filter,
1167        }
1168    }
1169}