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