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