cargo_e/
e_cargocommand_ext.rs

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