cargo_e/
e_processmanager.rs

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