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