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