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