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