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