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(¬e, "$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(¬e, "#[allow($1)]").to_string())
269 .unwrap_or_else(|_| note.clone());
270 let note = if self.uses_color {
271 Color::Blue.paint(¬e).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}