cargo_e/
e_processmanager.rs

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