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