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