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