cargo_e/e_processmanager.rs
1// src/e_process_manager.rs
2
3use crate::e_cargocommand_ext::{CargoProcessHandle, CargoProcessResult};
4use crate::e_command_builder::TerminalError;
5use crate::e_target::CargoTarget;
6use crate::Cli;
7use chrono::Local;
8use std::collections::HashMap;
9use std::process::ExitStatus;
10use std::sync::atomic::AtomicUsize;
11use std::sync::mpsc::{self, Receiver, Sender};
12use std::sync::{Arc, Mutex};
13use std::thread::{self, sleep};
14use std::time::{Duration, SystemTime};
15use sysinfo::System;
16// use std::io::Write;
17#[cfg(feature = "tui")]
18use crossterm::{
19 cursor, execute,
20 terminal::{Clear, ClearType},
21};
22use std::io::{self, Write};
23use std::sync::atomic::Ordering;
24use std::sync::Mutex as StdMutex;
25#[cfg(unix)]
26use {
27 nix::sys::signal::{kill as nix_kill, Signal},
28 nix::unistd::Pid,
29 std::os::unix::process::ExitStatusExt,
30};
31impl ProcessObserver for ProcessManager {
32 fn on_spawn(&self, pid: u32, handle: CargoProcessHandle) {
33 self.processes
34 .lock()
35 .unwrap()
36 .insert(pid, Arc::new(Mutex::new(handle)));
37 }
38 // let pid = handle.lock().unwrap().pid;
39 // self.processes.lock().unwrap().insert(pid, handle);
40 // Ok(())
41}
42
43// #[cfg(feature = "uses_async")]
44// use tokio::sync::Notify;
45
46// pub static PROCESS_MANAGER: Lazy<ProcessManager> = Lazy::new(ProcessManager::new);
47
48pub trait ProcessObserver: Send + Sync + 'static {
49 fn on_spawn(&self, pid: u32, handle: CargoProcessHandle);
50}
51pub trait SignalTimeTracker {
52 /// Returns the time when the last signal was received, if any.
53 fn last_signal_time(&self) -> Option<SystemTime>;
54 /// Returns the duration between the last two signals, if at least two signals were received.
55 fn time_between_signals(&self) -> Option<Duration>;
56}
57
58pub struct SignalTimes {
59 times: StdMutex<Vec<SystemTime>>,
60}
61
62impl SignalTimes {
63 pub fn new() -> Self {
64 Self {
65 times: StdMutex::new(Vec::new()),
66 }
67 }
68 pub fn record_signal(&self) {
69 let mut times = self.times.lock().unwrap();
70 times.push(SystemTime::now());
71 }
72}
73
74impl SignalTimeTracker for SignalTimes {
75 fn last_signal_time(&self) -> Option<SystemTime> {
76 let times = self.times.lock().unwrap();
77 times.last().cloned()
78 }
79 fn time_between_signals(&self) -> Option<Duration> {
80 let times = self.times.lock().unwrap();
81 if times.len() >= 2 {
82 let last = times[times.len() - 1];
83 let prev = times[times.len() - 2];
84 last.duration_since(prev).ok()
85 } else {
86 None
87 }
88 }
89}
90pub struct ProcessManager {
91 signalled_count: AtomicUsize,
92 signal_tx: Sender<()>,
93 processes: Mutex<HashMap<u32, Arc<Mutex<CargoProcessHandle>>>>,
94 results: Mutex<Vec<CargoProcessResult>>,
95 signal_times: SignalTimes, // <-- Add this line
96}
97
98impl ProcessManager {
99 pub fn new(_cli: &Cli) -> Arc<Self> {
100 let (tx, rx) = mpsc::channel();
101 let manager = Arc::new(Self {
102 signalled_count: AtomicUsize::new(0),
103 signal_tx: tx.clone(),
104 processes: Mutex::new(HashMap::new()),
105 results: Mutex::new(Vec::new()),
106 signal_times: SignalTimes::new(), // <-- Add this line
107 });
108 ProcessManager::install_handler(Arc::clone(&manager), rx);
109 manager
110 }
111
112 pub fn last_signal_time(&self) -> Option<SystemTime> {
113 self.signal_times.last_signal_time()
114 }
115 pub fn time_between_signals(&self) -> Option<Duration> {
116 self.signal_times.time_between_signals()
117 }
118 pub fn reset_signalled(&self) {
119 self.signalled_count.store(0, Ordering::SeqCst);
120 }
121 pub fn has_signalled(&self) -> usize {
122 self.signalled_count.load(Ordering::SeqCst)
123 }
124
125 fn install_handler(self_: Arc<Self>, rx: Receiver<()>) {
126 match ctrlc::set_handler({
127 let tx = self_.signal_tx.clone();
128 move || {
129 let _ = tx.send(());
130 }
131 }) {
132 Ok(_) => {
133 thread::spawn(move || {
134 while rx.recv().is_ok() {
135 self_.signalled_count.fetch_add(1, Ordering::SeqCst);
136 println!(
137 "ctrlc> signal received. {}",
138 self_.signalled_count.load(Ordering::SeqCst)
139 );
140 self_.handle_signal();
141 }
142 });
143 }
144 Err(e) => {
145 eprintln!("ctrlc> Failed to install Ctrl+C handler: {}", e);
146 return;
147 }
148 }
149 }
150
151 fn handle_signal(&self) {
152 self.signal_times.record_signal();
153 println!("ctrlc>");
154 let mut processes = self.processes.lock().unwrap();
155 for (pid, handle) in processes.iter() {
156 println!("ctrlc> Terminating process with PID: {}", pid);
157 if let Ok(mut h) = handle.lock() {
158 let _ = h.kill();
159 let final_diagnostics = {
160 let diag_lock = h.diagnostics.lock().unwrap();
161 diag_lock.clone()
162 };
163 h.result.diagnostics = final_diagnostics.clone();
164
165 if let Some(exit_status) = h.child.try_wait().ok().flatten() {
166 h.result.exit_status = Some(exit_status);
167 }
168
169 h.result.end_time = Some(SystemTime::now());
170 if let (Some(start), Some(end)) = (h.result.start_time, h.result.end_time) {
171 h.result.elapsed_time = Some(end.duration_since(start).unwrap_or_default());
172 }
173 self.record_result(h.result.clone());
174 }
175 }
176 processes.clear();
177 }
178
179 /// Updates the status line in the terminal.
180 /// When `raw_mode` is true, it uses crossterm commands to clear the current line.
181 /// Otherwise, it uses the carriage return (`\r`) trick.
182 pub fn update_status_line(output: &str, raw_mode: bool) -> io::Result<()> {
183 let mut stdout = io::stdout();
184 if raw_mode {
185 // Move cursor to beginning and clear the current line.
186 #[cfg(feature = "tui")]
187 {
188 execute!(
189 stdout,
190 cursor::MoveToColumn(0),
191 Clear(ClearType::CurrentLine)
192 )?;
193 print!("{}", output);
194 }
195 #[cfg(not(feature = "tui"))]
196 print!("\r{}", output);
197 } else {
198 // In non-raw mode, the \r trick can work.
199 print!("\r{}", output);
200 }
201 stdout.flush()
202 }
203
204 pub fn register(&self, handle: CargoProcessHandle) -> u32 {
205 let pid = handle.pid;
206 self.processes
207 .lock()
208 .unwrap()
209 .insert(pid, Arc::new(Mutex::new(handle)));
210
211 // #[cfg(feature = "uses_async")]
212 // self.notifier.notify_waiters();
213
214 pid
215 }
216
217 pub fn take(&self, pid: u32) -> Option<Arc<Mutex<CargoProcessHandle>>> {
218 self.processes.lock().unwrap().remove(&pid)
219 }
220
221 pub fn remove(&self, pid: u32) {
222 if let Some(handle_arc) = self.processes.lock().unwrap().remove(&pid) {
223 let mut h = handle_arc.lock().unwrap();
224 let final_diagnostics = {
225 let diag_lock = h.diagnostics.lock().unwrap();
226 diag_lock.clone()
227 };
228 h.result.diagnostics = final_diagnostics.clone();
229
230 // Ensure `es` is properly defined or assigned
231 if let Some(exit_status) = h.child.try_wait().ok().flatten() {
232 h.result.exit_status = Some(exit_status);
233 }
234
235 h.result.end_time = Some(SystemTime::now());
236 if let (Some(start), Some(end)) = (h.result.start_time, h.result.end_time) {
237 h.result.elapsed_time = Some(end.duration_since(start).unwrap_or_default());
238 }
239 self.record_result(h.result.clone());
240 drop(h);
241 // This was the only Arc reference, so dropping it here will run CargoProcessHandle::drop()
242 drop(handle_arc);
243 }
244 }
245
246 pub fn try_wait(&self, pid: u32) -> anyhow::Result<Option<ExitStatus>> {
247 // 1. Lock the processes map just long enough to clone the Arc.
248 let handle_arc = {
249 let processes = self.processes.lock().unwrap();
250 // Clone the Arc to keep the handle in the map while getting your own reference.
251 processes
252 .get(&pid)
253 .ok_or_else(|| anyhow::anyhow!("Process handle with PID {} not found", pid))?
254 .clone()
255 };
256
257 // 2. Lock the individual process handle to perform try_wait.
258 let mut handle = handle_arc.lock().unwrap();
259 // Here, try_wait returns a Result<Option<ExitStatus>, std::io::Error>.
260 // The '?' operator will convert any std::io::Error to anyhow::Error automatically.
261 let status = handle.child.try_wait()?;
262
263 // Return the exit status (or None) wrapped in Ok.
264 Ok(status)
265 }
266
267 pub fn get(&self, pid: u32) -> Option<Arc<Mutex<CargoProcessHandle>>> {
268 self.processes.lock().unwrap().get(&pid).cloned()
269 }
270
271 pub fn list(&self) -> Vec<u32> {
272 self.processes.lock().unwrap().keys().cloned().collect()
273 }
274
275 pub fn status(&self) {
276 let processes = self.processes.lock().unwrap();
277 if processes.is_empty() {
278 println!("No active cargo processes.");
279 } else {
280 println!("Active processes:");
281 for pid in processes.keys() {
282 println!(" - PID: {}", pid);
283 }
284 }
285 }
286
287 // /// Attempts to kill the process corresponding to the provided PID.
288 // /// Returns Ok(true) if the process was found and successfully killed (or had already exited),
289 // /// Ok(false) if the process was not found or did not exit within the maximum attempts,
290 // /// or an error if something went wrong.
291 // pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
292 // // Retrieve a clone of the handle (Arc) for the given PID without removing it.
293 // let handle = {
294 // let processes = self.processes.lock().unwrap();
295 // processes.get(&pid).cloned()
296 // };
297
298 // if let Some(handle) = handle {
299 // eprintln!("Attempting to kill PID: {}", pid);
300
301 // let max_attempts = 3;
302 // let mut attempts = 0;
303 // let mut process_exited = false;
304
305 // loop {
306 // // Lock the process handle for this iteration.
307 // if let Ok(mut h) = handle.lock() {
308 // // Check if the process has already exited.
309 // match h.child.try_wait() {
310 // Ok(Some(status)) => {
311 // eprintln!("Process {} already exited with status: {:?}", pid, status);
312 // process_exited = true;
313 // break;
314 // }
315 // Ok(None) => {
316 // // Process is still running.
317 // if attempts == 0 {
318 // #[cfg(not(target_os = "windows"))] {
319 // eprintln!("Sending initial Ctrl+C signal to PID: {}", pid);
320 // crate::e_runall::send_ctrl_c(&mut h.child)?;
321 // }
322 // #[cfg(target_os = "windows")] {
323 // eprintln!("Sending initial kill signal to PID: {}", pid);
324 // }
325 // } else {
326 // eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
327 // }
328 // // Attempt to kill the process.
329 // if let Err(e) = h.kill() {
330 // eprintln!("Failed to send kill signal to PID {}: {}", pid, e);
331 // }
332 // // Mark that an exit was requested.
333 // h.requested_exit = true;
334 // }
335 // Err(e) => {
336 // eprintln!("Error checking exit status for PID {}: {}", pid, e);
337 // break;
338 // }
339 // }
340 // } else {
341 // eprintln!("Could not lock process handle for PID {}", pid);
342 // break;
343 // }
344
345 // attempts += 1;
346 // // Allow some time for the process to exit.
347 // sleep(Duration::from_millis(2000));
348
349 // // Re-check if the process has exited.
350 // if let Ok(mut h) = handle.lock() {
351 // match h.child.try_wait() {
352 // Ok(Some(status)) => {
353 // eprintln!("Process {} exited with status: {:?}", pid, status);
354 // process_exited = true;
355 // break;
356 // }
357 // Ok(None) => {
358 // eprintln!("Process {} still running after attempt {}.", pid, attempts);
359 // }
360 // Err(e) => {
361 // eprintln!("Error rechecking process {}: {}", pid, e);
362 // break;
363 // }
364 // }
365 // }
366
367 // if attempts >= max_attempts {
368 // eprintln!("Maximum kill attempts reached for PID {}.", pid);
369 // break;
370 // }
371 // }
372
373 // // If the process exited, remove it from the map.
374 // if process_exited {
375 // let mut processes = self.processes.lock().unwrap();
376 // processes.remove(&pid);
377 // eprintln!("Process {} removed from map after exit.", pid);
378 // } else {
379 // eprintln!(
380 // "Process {} did not exit after {} attempts; it remains in the map for future handling.",
381 // pid, attempts
382 // );
383 // }
384 // Ok(process_exited)
385 // } else {
386 // eprintln!("Process handle with PID {} not found.", pid);
387 // Ok(false)
388 // }
389 // }
390
391 /// Attempts to kill the process corresponding to the provided PID.
392 /// Returns Ok(true) if the process was found and exited (even via signal),
393 /// Ok(false) if the process wasn’t found or didn’t exit after trying
394 /// all signals (in which case we drop the handle), or Err if something went wrong.
395 pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
396 // Grab the handle, if any.
397 let handle_opt = {
398 let procs = self.processes.lock().unwrap();
399 procs.get(&pid).cloned()
400 };
401
402 if let Some(handle) = handle_opt {
403 eprintln!("Attempting to kill PID: {}", pid);
404 #[cfg(unix)]
405 let signals = [
406 Signal::SIGHUP,
407 Signal::SIGINT,
408 Signal::SIGQUIT,
409 Signal::SIGABRT,
410 Signal::SIGKILL,
411 Signal::SIGALRM,
412 Signal::SIGTERM,
413 ];
414 #[cfg(unix)]
415 let max_attempts = signals.len();
416
417 #[cfg(windows)]
418 let max_attempts = 3; // arbitrary, since Child::kill() is always SIGKILL
419
420 let mut attempts = 0;
421 let mut did_exit = false;
422
423 while attempts < max_attempts {
424 // 1) Check status
425 if let Ok(mut h) = handle.lock() {
426 match h.child.try_wait() {
427 Ok(Some(status)) => {
428 // Child has exited—report how.
429 #[cfg(unix)]
430 {
431 if let Some(sig) = status.signal() {
432 eprintln!("Process {} terminated by signal {}", pid, sig);
433 } else if let Some(code) = status.code() {
434 eprintln!("Process {} exited with code {}", pid, code);
435 } else {
436 eprintln!(
437 "Process {} exited with unknown status: {:?}",
438 pid, status
439 );
440 }
441 }
442 #[cfg(not(unix))]
443 {
444 if let Some(code) = status.code() {
445 eprintln!("Process {} exited with code {}", pid, code);
446 } else {
447 eprintln!(
448 "Process {} exited with unknown status: {:?}",
449 pid, status
450 );
451 }
452 }
453 did_exit = true;
454 break;
455 }
456 Ok(None) => {
457 // Still running → send the next signal
458 #[cfg(unix)]
459 {
460 let sig = signals[attempts];
461 eprintln!(
462 "Attempt {}: sending {:?} to PID {}",
463 attempts + 1,
464 sig,
465 pid
466 );
467 nix_kill(Pid::from_raw(pid as i32), sig)?;
468 }
469 #[cfg(windows)]
470 {
471 // // Remove the handle so it drops (and on Windows that will kill if still alive)
472 // {
473 // let mut procs = self.processes.lock().unwrap();
474 // procs.remove(&pid);
475 // }
476
477 eprintln!("Attempt {}: killing PID {}", attempts + 1, pid);
478 if let Err(e) = h.child.kill() {
479 eprintln!("Failed to kill PID {}: {}", pid, e);
480 }
481 _ = std::process::Command::new("taskkill")
482 .args(["/F", "/PID", &pid.to_string()])
483 .spawn();
484 }
485 h.requested_exit = true;
486 }
487 Err(e) => {
488 eprintln!("Error checking status for PID {}: {}", pid, e);
489 break;
490 }
491 }
492 } else {
493 eprintln!("Could not lock handle for PID {}", pid);
494 break;
495 }
496
497 attempts += 1;
498 if did_exit {
499 break;
500 }
501
502 // Give it a moment before retrying
503 thread::sleep(Duration::from_secs(2));
504 }
505 // Remove the handle so it drops (and on Windows that will kill if still alive)
506 {
507 let mut procs = self.processes.lock().unwrap();
508 if let Some(handle_arc) = procs.remove(&pid) {
509 let mut handle = handle_arc.lock().unwrap();
510 let final_diagnostics = {
511 let diag_lock = handle.diagnostics.lock().unwrap();
512 diag_lock.clone()
513 };
514 handle.result.diagnostics = final_diagnostics.clone();
515
516 // Ensure `es` is properly defined or assigned
517 if let Some(exit_status) = handle.child.try_wait().ok().flatten() {
518 handle.result.exit_status = Some(exit_status);
519 }
520
521 handle.result.end_time = Some(SystemTime::now());
522 if let (Some(start), Some(end)) =
523 (handle.result.start_time, handle.result.end_time)
524 {
525 handle.result.elapsed_time =
526 Some(end.duration_since(start).unwrap_or_default());
527 }
528 self.record_result(handle.result.clone());
529 } else {
530 eprintln!("No process found with PID: {}", pid);
531 }
532 }
533
534 if did_exit {
535 eprintln!("Process {} removed from map after exit.", pid);
536 } else {
537 eprintln!(
538 "Dropped handle for PID {} after {} attempts (process may still be running).",
539 pid, attempts
540 );
541 }
542
543 Ok(did_exit)
544 } else {
545 eprintln!("Process handle with PID {} not found.", pid);
546 Ok(false)
547 }
548 }
549
550 // /// Attempts to kill the process corresponding to the provided PID.
551 // /// Returns Ok(true) if the process was found and successfully killed
552 // /// (or had already exited), Ok(false) if the process was not found
553 // /// or did not exit within the maximum attempts (in which case we drop
554 // /// the handle), or an error if something went wrong.
555 // pub fn kill_by_pid(&self, pid: u32) -> anyhow::Result<bool> {
556 // // Grab a clone of the Arc<Mutex<ProcessHandle>> if it exists
557 // let handle_opt = {
558 // let processes = self.processes.lock().unwrap();
559 // processes.get(&pid).cloned()
560 // };
561
562 // if let Some(handle) = handle_opt {
563 // eprintln!("Attempting to kill PID: {}", pid);
564
565 // let max_attempts = 3;
566 // let mut attempts = 0;
567 // let mut process_exited = false;
568
569 // loop {
570 // // 1) Check status / send signal
571 // if let Ok(mut h) = handle.lock() {
572 // match h.child.try_wait() {
573 // Ok(Some(status)) => {
574 // // Already exited
575 // eprintln!(
576 // "Process {} already exited with status: {:?}",
577 // pid, status
578 // );
579 // process_exited = true;
580 // break;
581 // }
582 // Ok(None) => {
583 // // Still running → send signal
584 // if attempts == 0 {
585 // #[cfg(not(target_os = "windows"))]
586 // {
587 // eprintln!("Sending initial Ctrl+C to PID: {}", pid);
588 // crate::e_runall::send_ctrl_c(&mut h.child)?;
589 // }
590 // #[cfg(target_os = "windows")]
591 // {
592 // eprintln!("Sending initial kill to PID: {}", pid);
593 // }
594 // } else {
595 // eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
596 // }
597
598 // if let Err(e) = h.kill() {
599 // eprintln!("Failed to send kill to PID {}: {}", pid, e);
600 // }
601 // h.requested_exit = true;
602 // }
603 // Err(e) => {
604 // eprintln!("Error checking status for PID {}: {}", pid, e);
605 // break;
606 // }
607 // }
608 // } else {
609 // eprintln!("Could not lock handle for PID {}", pid);
610 // break;
611 // }
612
613 // attempts += 1;
614 // if attempts >= max_attempts {
615 // eprintln!("Maximum kill attempts reached for PID {}. Dropping handle.", pid);
616 // break;
617 // }
618
619 // // 2) Wait a bit before re-checking
620 // thread::sleep(Duration::from_millis(2_000));
621
622 // // 3) Re-check exit status
623 // if let Ok(mut h) = handle.lock() {
624 // match h.child.try_wait() {
625 // Ok(Some(status)) => {
626 // eprintln!("Process {} exited with status: {:?}", pid, status);
627 // process_exited = true;
628 // break;
629 // }
630 // Ok(None) => {
631 // eprintln!("Process {} still running after attempt {}.", pid, attempts);
632 // }
633 // Err(e) => {
634 // eprintln!("Error rechecking process {}: {}", pid, e);
635 // break;
636 // }
637 // }
638 // }
639 // }
640
641 // // Remove the handle (dropping it) whether or not the process exited
642 // {
643 // let mut processes = self.processes.lock().unwrap();
644 // processes.remove(&pid);
645 // }
646
647 // if process_exited {
648 // eprintln!("Process {} removed from map after exit.", pid);
649 // } else {
650 // eprintln!(
651 // "Dropped handle for PID {} after {} attempts (process may still be running).",
652 // pid, attempts
653 // );
654 // }
655
656 // Ok(process_exited)
657 // } else {
658 // eprintln!("Process handle with PID {} not found.", pid);
659 // Ok(false)
660 // }
661 // }
662
663 /// Attempts to kill one process.
664 /// Returns Ok(true) if a process was found and killed, Ok(false) if none found,
665 /// or an error if something went wrong.
666 pub fn kill_one(&self) -> anyhow::Result<bool> {
667 // First, lock the map briefly to pick one process handle.
668 let maybe_entry = {
669 let processes = self.processes.lock().unwrap();
670 // Clone the Arc so that we don’t take ownership from the map.
671 processes
672 .iter()
673 .next()
674 .map(|(&pid, handle)| (pid, handle.clone()))
675 };
676
677 if let Some((pid, handle)) = maybe_entry {
678 eprintln!("Attempting to kill PID: {}", pid);
679
680 // We'll attempt to kill the process up to `max_attempts` times.
681 let max_attempts = 3;
682 let mut attempts = 0;
683 let mut process_exited = false;
684
685 loop {
686 // Lock the process handle for this iteration.
687 if let Ok(mut h) = handle.lock() {
688 // Check if the process has already exited.
689 match h.child.try_wait() {
690 Ok(Some(status)) => {
691 eprintln!("Process {} already exited with status: {:?}", pid, status);
692 process_exited = true;
693 sleep(Duration::from_millis(3_000));
694 break;
695 }
696 Ok(None) => {
697 // Process is still running. On the first attempt, or forcefully on later attempts.
698 if attempts == 0 {
699 eprintln!("Sending initial kill signal to PID: {}", pid);
700 } else {
701 eprintln!("Attempt {}: Forcing kill for PID: {}", attempts, pid);
702 }
703 sleep(Duration::from_millis(3_000));
704 // Try to kill the process. Handle errors by printing a debug message.
705 if let Err(e) = h.kill() {
706 eprintln!("Failed to send kill signal to PID {}: {}", pid, e);
707 sleep(Duration::from_millis(3_000));
708 }
709 }
710 Err(e) => {
711 eprintln!("Error checking exit status for PID {}: {}", pid, e);
712 sleep(Duration::from_millis(3_000));
713 break;
714 }
715 }
716 } else {
717 eprintln!("Could not lock process handle for PID {}", pid);
718 sleep(Duration::from_millis(3_000));
719 break;
720 }
721
722 attempts += 1;
723 // Allow some time for the process to exit.
724 sleep(Duration::from_millis(3_000));
725
726 // Check again after the sleep.
727 if let Ok(mut h) = handle.lock() {
728 match h.child.try_wait() {
729 Ok(Some(status)) => {
730 eprintln!("Process {} exited with status: {:?}", pid, status);
731 sleep(Duration::from_millis(3_000));
732 process_exited = true;
733 break;
734 }
735 Ok(None) => {
736 eprintln!("Process {} still running after attempt {}.", pid, attempts);
737 sleep(Duration::from_millis(3_000));
738 }
739 Err(e) => {
740 eprintln!("Error rechecking process {}: {}", pid, e);
741 sleep(Duration::from_millis(3_000));
742 break;
743 }
744 }
745 }
746
747 if attempts >= max_attempts {
748 eprintln!("Maximum kill attempts reached for PID {}.", pid);
749 sleep(Duration::from_millis(3_000));
750 break;
751 }
752 }
753
754 // 4) In all cases, remove the handle so it drops
755 {
756 let mut ps = self.processes.lock().unwrap();
757 ps.remove(&pid);
758 }
759 if process_exited {
760 eprintln!("Process {} removed from map after exit.", pid);
761 } else {
762 eprintln!(
763 "Dropped handle for PID {} after {} attempts (process may still be running).",
764 pid, attempts
765 );
766 }
767 sleep(Duration::from_millis(3_000));
768 Ok(process_exited)
769 } else {
770 println!("No processes to kill.");
771 sleep(Duration::from_millis(3_000));
772 Ok(false)
773 }
774 }
775 // pub fn kill_one(&self) {
776 // let mut processes = self.processes.lock().unwrap();
777 // if let Some((&pid, handle)) = processes.iter().next() {
778 // eprintln!("Killing PID: {}", pid);
779 // if let Ok(mut h) = handle.lock() {
780 // let _ = h.kill();
781 // }
782 // processes.remove(&pid);
783 // } else {
784 // println!("No processes to kill.");
785 // }
786 // }
787
788 pub fn kill_all(&self) {
789 let mut processes = self.processes.lock().unwrap();
790 for (pid, handle) in processes.drain() {
791 eprintln!("Killing PID: {}", pid);
792 if let Ok(mut h) = handle.lock() {
793 let _ = h.kill();
794 }
795 }
796 }
797
798 // Returns the terminal error for a given PID.
799 pub fn get_terminal_error(&self, pid: u32) -> Option<TerminalError> {
800 // Lock the process map
801 let processes = self.processes.lock().unwrap();
802
803 // Check if the process exists
804 if let Some(handle) = processes.get(&pid) {
805 // Lock the handle to access the terminal error flag
806 let handle = handle.lock().unwrap();
807 // Return the terminal error flag value
808 return Some(handle.terminal_error_flag.lock().unwrap().clone());
809 }
810
811 // If no process is found for the given PID, return None
812 None
813 }
814
815 // pub fn install_ctrlc_handler(self: Arc<Self>) {
816 // let manager = Arc::clone(&self);
817 // ctrlc::set_handler(move || {
818 // eprintln!("CTRL-C detected. Killing all processes.");
819 // manager.kill_all();
820 // std::process::exit(1);
821 // })
822 // .expect("Failed to install ctrl-c handler");
823 // }
824
825 /// Wait for the process to finish, show interactive status, then return a result
826 pub fn wait(
827 &self,
828 pid: u32,
829 _duration: Option<Duration>,
830 ) -> anyhow::Result<CargoProcessResult> {
831 // Hide the cursor and ensure it’s restored on exit
832 #[allow(dead_code)]
833 struct CursorHide;
834 impl Drop for CursorHide {
835 fn drop(&mut self) {
836 #[cfg(feature = "tui")]
837 {
838 let _ = crossterm::execute!(std::io::stdout(), crossterm::cursor::Show);
839 }
840 }
841 }
842 #[cfg(feature = "tui")]
843 let _cursor_hide = {
844 let mut out = std::io::stdout();
845 crossterm::execute!(out, crossterm::cursor::Hide)?;
846 CursorHide
847 };
848
849 // 1. Remove the handle from the map
850 let handle_arc = {
851 let mut map = self.processes.lock().unwrap();
852 map.remove(&pid).ok_or_else(|| {
853 let result = CargoProcessResult {
854 target_name: String::new(), // Placeholder, should be set properly in actual use
855 cmd: String::new(), // Placeholder, should be set properly in actual use
856 args: Vec::new(), // Placeholder, should be set properly in actual use
857 pid,
858 exit_status: None,
859 diagnostics: Vec::new(),
860 start_time: None,
861 end_time: Some(SystemTime::now()),
862 elapsed_time: None,
863 terminal_error: None, // Placeholder, should be set properly in actual use
864 build_finished_time: None, // Placeholder, should be set properly in actual use
865 build_elapsed: None, // Placeholder, should be set properly in actual use
866 runtime_elapsed: None, // Placeholder, should be set properly in actual use
867 stats: crate::e_cargocommand_ext::CargoStats::default(), // Provide a default instance of CargoStats
868 build_output_size: 0, // Default value set to 0
869 runtime_output_size: 0, // Placeholder, should be set properly in actual use
870 is_filter: false, // Placeholder, should be set properly in actual use
871 is_could_not_compile: false, // Placeholder, should be set properly in actual use
872 };
873 self.record_result(result.clone());
874 anyhow::anyhow!("Process handle with PID {} not found", pid)
875 })?
876 };
877
878 // 2. Unwrap Arc<Mutex<...>> to get the handle
879 let mut handle = Arc::try_unwrap(handle_arc)
880 .map_err(|_| anyhow::anyhow!("Process handle for PID {} still shared", pid))?
881 .into_inner()
882 .unwrap();
883
884 // 3. Interactive polling loop
885 let mut system = if handle.is_filter {
886 Some(System::new_all())
887 } else {
888 None
889 };
890 const POLL: Duration = Duration::from_secs(1);
891 let mut loop_cnter = 0;
892 loop {
893 loop_cnter += 1;
894 if handle.is_filter && loop_cnter % 2 == 0 {
895 if let Some(ref mut sys) = system {
896 sys.refresh_all();
897 }
898 }
899
900 if handle.is_filter {
901 if let Some(ref sys) = system {
902 if let Some(process) = sys.process((pid as usize).into()) {
903 let status = handle.format_status(Some(process));
904 if !status.is_empty() {
905 print!("\r{}", status);
906 }
907 }
908 }
909 std::io::stdout().flush().unwrap();
910 }
911
912 if let Some(es) = handle.child.try_wait()? {
913 let final_diagnostics = {
914 let diag_lock = handle.diagnostics.lock().unwrap();
915 diag_lock.clone()
916 };
917 handle.result.diagnostics = final_diagnostics.clone();
918 handle.result.exit_status = Some(es);
919 handle.result.end_time = Some(SystemTime::now());
920 if let (Some(start), Some(end)) = (handle.result.start_time, handle.result.end_time)
921 {
922 handle.result.elapsed_time =
923 Some(end.duration_since(start).unwrap_or_default());
924 }
925 println!(
926 "\nProcess with PID {} finished {:?} {}",
927 pid,
928 es,
929 handle.result.diagnostics.len()
930 );
931 break;
932 }
933 std::thread::sleep(POLL);
934 }
935
936 if handle.is_filter {
937 // 4. Extract diagnostics out of Arc<Mutex<_>>
938 let diagnostics = Arc::try_unwrap(handle.diagnostics)
939 .map(|m| m.into_inner().unwrap())
940 .unwrap_or_else(|arc| arc.lock().unwrap().clone());
941
942 // 5. Move them into the final result
943 handle.result.diagnostics = diagnostics;
944 }
945 self.record_result(handle.result.clone());
946 Ok(handle.result)
947 }
948
949 pub fn record_result(&self, result: CargoProcessResult) {
950 let mut results = self.results.lock().unwrap();
951 results.push(result);
952 }
953
954 pub fn generate_report(&self, create_gist: bool) {
955 let results = self.results.lock().unwrap();
956 let report = crate::e_reports::generate_markdown_report(&results);
957 if let Err(e) = crate::e_reports::save_report_to_file(&report, "run_report.md") {
958 eprintln!("Failed to save report: {}", e);
959 }
960 if create_gist {
961 crate::e_reports::create_gist(&report, "run_report.md").unwrap_or_else(|e| {
962 eprintln!("Failed to create Gist: {}", e);
963 });
964 }
965 }
966 // pub fn wait(&self, pid: u32, _duration: Option<Duration>) -> anyhow::Result<CargoProcessResult> {
967 // // Hide the cursor and ensure it is restored on exit.
968 // {
969 // let mut stdout = std::io::stdout();
970 // crossterm::execute!(stdout, crossterm::cursor::Hide)?;
971 // }
972
973 // let mut processes = self.processes.lock().unwrap();
974 // if let Some(handle) = processes.get_mut(&pid) {
975 // let mut handle = handle.lock().unwrap();
976 // let mut system = System::new_all();
977
978 // // Initialize start_time if not already set.
979 // let start_time = handle.result.start_time.unwrap_or_else(|| {
980 // let now = SystemTime::now();
981 // handle.result.start_time = Some(now);
982 // now
983 // });
984
985 // // Main loop.
986 // const POLL_DURATION: Duration = Duration::from_secs(1);
987 // loop {
988 // system.refresh_all();
989 // let maybe_system: Option<&System> = if true { Some(&system) } else { None };
990 // // Get formatted status string.
991 // let output = handle.format_status(maybe_system);
992 // print!("\r{}", output);
993 // std::io::stdout().flush().unwrap();
994
995 // if let Some(status) = handle.child.try_wait()? {
996 // handle.result.exit_status = Some(status);
997 // handle.result.end_time = Some(SystemTime::now());
998 // println!("\nProcess with PID {} finished", pid);
999 // return Ok(handle.result.clone());
1000 // }
1001 // std::thread::sleep(POLL_DURATION);
1002 // }
1003 // } else {
1004 // Err(anyhow::anyhow!("Process handle with PID {} not found", pid))
1005 // }
1006 // }
1007
1008 // pub fn wait(&self, pid: u32, _duration: Option<Duration>) -> anyhow::Result<CargoProcessResult> {
1009 // // Turn off (hide) the cursor.
1010 // {
1011 // let mut stdout = std::io::stdout();
1012 // crossterm::execute!(stdout, crossterm::cursor::Hide)?;
1013 // }
1014 // // Ensure the cursor is shown when we exit.
1015 // let _cursor_guard = CursorGuard;
1016
1017 // let mut processes = self.processes.lock().unwrap();
1018 // if let Some(handle) = processes.get_mut(&pid) {
1019 // let mut handle = handle.lock().unwrap();
1020 // let mut system = System::new_all();
1021
1022 // // Define the poll duration constant (adjust as needed).
1023 // const POLL_DURATION: Duration = Duration::from_secs(1);
1024
1025 // // Initialize start_time if not already set.
1026 // let start_time = handle.result.start_time.unwrap_or_else(|| {
1027 // let now = SystemTime::now();
1028 // handle.result.start_time = Some(now);
1029 // now
1030 // });
1031 // // Format the start time with seconds precision.
1032 // let start_dt: chrono::DateTime<Local> = start_time.into();
1033 // let start_str = start_dt.format("%H:%M:%S").to_string();
1034 // // Use ANSI color for the start time.
1035 // let colored_start = nu_ansi_term::Color::Green.paint(&start_str).to_string();
1036 // // Plain version for spacing calculations.
1037 // let plain_start = start_str;
1038
1039 // loop {
1040 // system.refresh_all();
1041 // let now = SystemTime::now();
1042 // let runtime_duration = now.duration_since(start_time).unwrap();
1043 // let runtime_str = crate::e_fmt::format_duration(runtime_duration);
1044
1045 // // Use usize conversion with into()
1046 // if let Some(process) = system.process((pid as usize).into()) {
1047 // let cpu_usage = process.cpu_usage();
1048 // let mem_kb = process.memory();
1049 // let mem_human = if mem_kb >= 1024 {
1050 // format!("{:.2} MB", mem_kb as f64 / 1024.0)
1051 // } else {
1052 // format!("{} KB", mem_kb)
1053 // };
1054
1055 // let left_display = format!("{} | CPU: {:.2}% | Mem: {}", colored_start, cpu_usage, mem_human);
1056 // let left_plain = format!("{} | CPU: {:.2}% | Mem: {}", plain_start, cpu_usage, mem_human);
1057
1058 // // Get terminal width with crossterm.
1059 // let (cols, _) = crossterm::terminal::size().unwrap_or((80, 20));
1060 // let total_width = cols as usize;
1061 // // Right side: the elapsed duration, underlined.
1062 // let right_display = nu_ansi_term::Style::new()
1063 // .reset_before_style()
1064 // .underline()
1065 // .paint(&runtime_str)
1066 // .to_string();
1067 // let left_len = left_plain.len();
1068 // let right_len = runtime_str.len();
1069 // let padding = if total_width > left_len + right_len {
1070 // total_width - left_len - right_len
1071 // } else {
1072 // 1
1073 // };
1074
1075 // let output = format!("\r{}{}{}", left_display, " ".repeat(padding), right_display);
1076 // print!("{}", output);
1077 // std::io::stdout().flush().unwrap();
1078 // } else {
1079 // print!("\rProcess with PID {} not found in sysinfo", pid);
1080 // std::io::stdout().flush().unwrap();
1081 // }
1082
1083 // if let Some(status) = handle.child.try_wait()? {
1084 // handle.result.exit_status = Some(status);
1085 // handle.result.end_time = Some(SystemTime::now());
1086 // println!("\nProcess with PID {} finished", pid);
1087 // return Ok(handle.result.clone());
1088 // }
1089
1090 // std::thread::sleep(POLL_DURATION);
1091 // }
1092 // } else {
1093 // Err(anyhow::anyhow!("Process handle with PID {} not found", pid))
1094 // }
1095 // }
1096
1097 // pub fn wait(&self, pid: u32, max_polls: Option<usize>) -> anyhow::Result<CargoProcessResult> {
1098 // let mut processes = self.processes.lock().unwrap();
1099 // if let Some(handle) = processes.get_mut(&pid) {
1100 // let mut handle = handle.lock().unwrap();
1101 // let mut system = System::new_all();
1102
1103 // // Initialize start_time if not already set.
1104 // let start_time = handle.result.start_time.unwrap_or_else(|| {
1105 // let now = SystemTime::now();
1106 // handle.result.start_time = Some(now);
1107 // now
1108 // });
1109 // // Format the start time with more precision.
1110 // let start_dt: chrono::DateTime<Local> = start_time.into();
1111 // let start_str = start_dt.format("%H:%M:%S").to_string();
1112
1113 // let mut polls = 0;
1114 // loop {
1115 // system.refresh_all();
1116
1117 // if let Some(process) = system.process((pid as usize).into()) {
1118 // let now = SystemTime::now();
1119 // let runtime = now.duration_since(start_time).unwrap();
1120 // let runtime_str = Self::format_duration(runtime);
1121
1122 // // Get memory usage and convert to a human-readable string.
1123 // let mem_kb = process.memory();
1124 // let mem_human = if mem_kb >= 1024 {
1125 // format!("{:.2} MB", mem_kb as f64 / 1024.0)
1126 // } else {
1127 // format!("{} KB", mem_kb)
1128 // };
1129
1130 // // Build the output string.
1131 // let output = format!(
1132 // "{} | Runtime: {} | Mem: {} | CPU: {:.2}%%",
1133 // start_str,
1134 // runtime_str,
1135 // mem_human,
1136 // process.cpu_usage()
1137 // );
1138 // // Print on one line and pad to clear leftover characters.
1139 // print!("\r{:<80}", output);
1140 // std::io::stdout().flush().unwrap();
1141 // } else {
1142 // print!("\rProcess with PID {} not found in sysinfo", pid);
1143 // std::io::stdout().flush().unwrap();
1144 // }
1145
1146 // // Check if the process has finished.
1147 // if let Some(status) = handle.child.try_wait()? {
1148 // handle.result.exit_status = Some(status);
1149 // handle.result.end_time = Some(SystemTime::now());
1150 // println!("\nProcess with PID {} finished", pid);
1151 // return Ok(handle.result.clone());
1152 // }
1153
1154 // polls += 1;
1155 // if let Some(max) = max_polls {
1156 // if polls >= max {
1157 // println!("\nReached maximum polling iterations ({})", max);
1158 // break;
1159 // }
1160 // }
1161 // std::thread::sleep(Duration::from_secs(1));
1162 // }
1163 // Err(anyhow::anyhow!("Process did not finish after maximum polls"))
1164 // } else {
1165 // Err(anyhow::anyhow!("Process handle with PID {} not found", pid))
1166 // }
1167 // }
1168
1169 // pub fn wait(&self, pid: u32) -> anyhow::Result<CargoProcessResult> {
1170 // let mut processes = self.processes.lock().unwrap();
1171 // if let Some(handle) = processes.get_mut(&pid) {
1172 // let mut handle = handle.lock().unwrap();
1173
1174 // loop {
1175 // println!("Waiting for process with PID: {}", pid);
1176
1177 // let status = handle.child.try_wait()?;
1178
1179 // if let Some(status) = status {
1180 // handle.result.exit_status = Some(status);
1181 // handle.result.end_time = Some(SystemTime::now());
1182 // println!("Process with PID {} finished", pid);
1183 // return Ok(handle.result.clone());
1184 // }
1185
1186 // std::thread::sleep(std::time::Duration::from_secs(1));
1187 // }
1188 // } else {
1189 // Err(anyhow::anyhow!("Process handle with PID {} not found", pid))
1190 // }
1191 // }
1192
1193 pub fn format_process_status(
1194 pid: u32,
1195 start_time: Option<SystemTime>,
1196 system: Arc<Mutex<System>>,
1197 target: &CargoTarget,
1198 target_dimensions: (usize, usize),
1199 ) -> String {
1200 // let start_dt: chrono::DateTime<Local> =
1201 // start_time.unwrap_or_else(|| SystemTime::UNIX_EPOCH).into();
1202 let start_str = start_time
1203 .map(|st| {
1204 chrono::DateTime::<Local>::from(st)
1205 .format("%H:%M:%S")
1206 .to_string()
1207 })
1208 .unwrap_or_else(|| "-".to_string());
1209 let colored_start = nu_ansi_term::Color::LightCyan.paint(&start_str).to_string();
1210 let plain_start = start_str;
1211 if start_time.is_none() {
1212 return String::new();
1213 }
1214 // Refresh the system stats and look up the process.
1215 if let Some(process) = system.lock().unwrap().process((pid as usize).into()) {
1216 let cpu_usage = process.cpu_usage();
1217 let mem_kb = process.memory();
1218 let mem_human = if mem_kb >= 1024 {
1219 format!("{:.2} MB", mem_kb as f64 / 1024.0)
1220 } else {
1221 format!("{} KB", mem_kb)
1222 };
1223
1224 // Calculate runtime.
1225 let now = SystemTime::now();
1226 let runtime_duration = match start_time {
1227 Some(start) => now
1228 .duration_since(start)
1229 .unwrap_or_else(|_| Duration::from_secs(0)),
1230 None => Duration::from_secs(0),
1231 };
1232 let runtime_str = crate::e_fmt::format_duration(runtime_duration);
1233 // compute the max number of digits in either dimension:
1234 let max_digits = target_dimensions
1235 .0
1236 .max(target_dimensions.1)
1237 .to_string()
1238 .len();
1239 let left_display = format!(
1240 "{:0width$}of{:0width$} | {} | {} | PID: {} | CPU: {:.2}% | Mem: {}",
1241 target_dimensions.0,
1242 target_dimensions.1,
1243 nu_ansi_term::Color::Green
1244 .paint(target.display_name.clone())
1245 .to_string(),
1246 colored_start,
1247 pid,
1248 cpu_usage,
1249 mem_human,
1250 width = max_digits,
1251 );
1252 let left_plain = format!(
1253 "{:0width$}of{:0width$} | {} | {} | PID: {} | CPU: {:.2}% | Mem: {}",
1254 target_dimensions.0,
1255 target_dimensions.1,
1256 target.display_name,
1257 plain_start,
1258 pid,
1259 cpu_usage,
1260 mem_human,
1261 width = max_digits,
1262 );
1263
1264 // Get terminal width.
1265 #[cfg(feature = "tui")]
1266 let (cols, _) = crossterm::terminal::size().unwrap_or((80, 20));
1267 #[cfg(not(feature = "tui"))]
1268 let (cols, _) = (80, 20);
1269 let total_width = cols as usize;
1270
1271 // Format the runtime with underlining.
1272 let right_display = nu_ansi_term::Style::new()
1273 .reset_before_style()
1274 .underline()
1275 .paint(&runtime_str)
1276 .to_string();
1277 let left_len = left_plain.len();
1278 let right_len = runtime_str.len();
1279 let padding = if total_width > left_len + right_len {
1280 total_width - left_len - right_len
1281 } else {
1282 1
1283 };
1284
1285 format!("{}{}{}", left_display, " ".repeat(padding), right_display)
1286 } else {
1287 // String::from("xxx")
1288 format!("\rProcess with PID {} not found in sysinfo", pid)
1289 }
1290 }
1291
1292 /// Print the exact diagnostic output as captured.
1293 pub fn print_exact_output(&self) {
1294 let processes = self.processes.lock().unwrap();
1295 for handle in processes.iter() {
1296 println!("--- Full Diagnostics for PID {} ---", handle.0);
1297 let handle_lock = handle.1.lock().unwrap();
1298 let diags = handle_lock.diagnostics.lock().unwrap();
1299 for diag in diags.iter() {
1300 // Print the entire diagnostic.
1301 println!("{:?}: {}", diag.level, diag.message);
1302 }
1303 }
1304 }
1305
1306 /// Print a one‑line summary per warning, numbered with leading zeros.
1307 pub fn print_prefixed_summary(&self) {
1308 // 1. Grab a snapshot of the handles (Arc clones) under the manager lock.
1309 let guard = self.processes.lock().unwrap();
1310 let handles: Vec<_> = guard.iter().map(|h| h.clone()).collect();
1311
1312 // 2. Now we can iterate without holding the manager lock.
1313 for handle in handles {
1314 // Lock only the diagnostics for this handle.
1315 let handle_lock = handle.1.lock().unwrap();
1316 let diags = handle_lock.diagnostics.lock().unwrap();
1317
1318 // Collect warnings.
1319 let warnings: Vec<_> = diags.iter().filter(|d| d.level.eq("warning")).collect();
1320
1321 // Determine width for zero-padding for warnings.
1322 let warning_width = warnings.len().to_string().len().max(1);
1323 println!(
1324 "\n\n--- Warnings for PID {} --- {} {}",
1325 handle.0,
1326 warning_width,
1327 warnings.len()
1328 );
1329
1330 for (i, diag) in warnings.iter().enumerate() {
1331 // Format the index with leading zeros for warnings.
1332 let index = format!("{:0width$}", i + 1, width = warning_width);
1333 // Print the warning with the index.
1334 println!("{}: {}", index, diag.message.trim());
1335 }
1336
1337 // Collect errors.
1338 let errors: Vec<_> = diags.iter().filter(|d| d.level.eq("error")).collect();
1339
1340 // Determine width for zero-padding for errors.
1341 let error_width = errors.len().to_string().len().max(1);
1342 println!(
1343 "\n\n--- Errors for PID {} --- {} {}",
1344 handle.0,
1345 error_width,
1346 errors.len()
1347 );
1348
1349 for (i, diag) in errors.iter().enumerate() {
1350 // Format the index with leading zeros for errors.
1351 let index = format!("{:0width$}", i + 1, width = error_width);
1352 // Print the error with the index.
1353 println!("{}: {}", index, diag.message.trim());
1354 }
1355 }
1356 }
1357
1358 /// file:line:col – source_line, colored by level.
1359 pub fn print_compact(&self) {
1360 let processes = self.processes.lock().unwrap();
1361 for handle in processes.iter() {
1362 println!("--- Compact for PID {} ---", handle.0);
1363 let handle_lock = handle.1.lock().unwrap();
1364 let diags = handle_lock.diagnostics.lock().unwrap();
1365 for diag in diags.iter() {
1366 println!("{}: {} {}", diag.level, diag.lineref, diag.message.trim());
1367 }
1368 }
1369 }
1370 /// Print a shortened version: warnings first then errors.
1371 pub fn print_shortened_output(&self) {
1372 let processes = self.processes.lock().unwrap();
1373 for handle in processes.iter() {
1374 println!("\n\n\n--- Summary for PID {} ---", handle.0);
1375 let handle_lock = handle.1.lock().unwrap();
1376 let diags = handle_lock.diagnostics.lock().unwrap();
1377
1378 // Filter diagnostics for warnings and errors.
1379 let warnings: Vec<_> = diags.iter().filter(|d| d.level.eq("warning")).collect();
1380 let errors: Vec<_> = diags.iter().filter(|d| d.level.eq("error")).collect();
1381
1382 // Print warnings.
1383 if !warnings.is_empty() {
1384 println!("Warnings:");
1385 for diag in warnings {
1386 println!("print_shortened_output:{}", diag.message.trim());
1387 }
1388 } else {
1389 println!("No warnings.");
1390 }
1391
1392 // Print errors.
1393 if !errors.is_empty() {
1394 println!("Errors:");
1395 for diag in errors {
1396 println!("print_shortened_output: {}", diag.message.trim());
1397 }
1398 } else {
1399 println!("No errors.");
1400 }
1401 }
1402 }
1403}
1404
1405// #[cfg(feature = "uses_async")]
1406// impl ProcessManager {
1407// pub async fn wait_for_processes(&self) {
1408// loop {
1409// {
1410// if self.processes.lock().unwrap().is_empty() {
1411// break;
1412// }
1413// }
1414// self.notifier.notified().await;
1415// }
1416// }
1417// }
1418
1419pub struct CursorGuard;
1420
1421impl Drop for CursorGuard {
1422 fn drop(&mut self) {
1423 #[cfg(feature = "tui")]
1424 {
1425 let mut stdout = std::io::stdout();
1426 let _ = crossterm::execute!(stdout, crossterm::cursor::Show);
1427 }
1428 }
1429}