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