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