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