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}