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