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