proc_cli/commands/
kill.rs1use crate::core::{parse_targets, resolve_targets_excluding_self, Process};
12use crate::error::{ProcError, Result};
13use crate::ui::{OutputFormat, Printer};
14use clap::Args;
15use dialoguer::Confirm;
16use std::path::PathBuf;
17
18#[derive(Args, Debug)]
20pub struct KillCommand {
21 pub target: String,
23
24 #[arg(long, short = 'y')]
26 pub yes: bool,
27
28 #[arg(long)]
30 pub dry_run: bool,
31
32 #[arg(long, short = 'j')]
34 pub json: bool,
35
36 #[arg(long, short = 'v')]
38 pub verbose: bool,
39
40 #[arg(long, short = 'g')]
42 pub graceful: bool,
43
44 #[arg(long = "in", short = 'i', num_args = 0..=1, default_missing_value = ".")]
46 pub in_dir: Option<String>,
47
48 #[arg(long = "by", short = 'b')]
50 pub by_name: Option<String>,
51}
52
53impl KillCommand {
54 pub fn execute(&self) -> Result<()> {
56 let format = if self.json {
57 OutputFormat::Json
58 } else {
59 OutputFormat::Human
60 };
61 let printer = Printer::new(format, self.verbose);
62
63 let targets = parse_targets(&self.target);
66 let (mut processes, not_found) = resolve_targets_excluding_self(&targets);
67
68 for target in ¬_found {
70 printer.warning(&format!("Target not found: {}", target));
71 }
72
73 let in_dir_filter = resolve_in_dir(&self.in_dir);
75 processes.retain(|p| {
76 if let Some(ref dir_path) = in_dir_filter {
77 if let Some(ref cwd) = p.cwd {
78 if !PathBuf::from(cwd).starts_with(dir_path) {
79 return false;
80 }
81 } else {
82 return false;
83 }
84 }
85 if let Some(ref name) = self.by_name {
86 if !p.name.to_lowercase().contains(&name.to_lowercase()) {
87 return false;
88 }
89 }
90 true
91 });
92
93 if processes.is_empty() {
94 return Err(ProcError::ProcessNotFound(self.target.clone()));
95 }
96
97 if self.dry_run {
99 printer.print_processes(&processes);
100 printer.warning(&format!(
101 "Dry run: would kill {} process{}",
102 processes.len(),
103 if processes.len() == 1 { "" } else { "es" }
104 ));
105 return Ok(());
106 }
107
108 if !self.yes && !self.json {
110 self.print_confirmation_prompt(&processes);
111
112 let confirmed = Confirm::new()
113 .with_prompt(format!(
114 "Kill {} process{}?",
115 processes.len(),
116 if processes.len() == 1 { "" } else { "es" }
117 ))
118 .default(false)
119 .interact()
120 .unwrap_or(false);
121
122 if !confirmed {
123 printer.warning("Cancelled");
124 return Ok(());
125 }
126 }
127
128 let mut killed = Vec::new();
130 let mut failed = Vec::new();
131
132 for proc in processes {
133 let result = if self.graceful {
134 proc.terminate()
135 } else {
136 proc.kill()
137 };
138
139 match result {
140 Ok(()) => killed.push(proc),
141 Err(e) => failed.push((proc, e.to_string())),
142 }
143 }
144
145 printer.print_kill_result(&killed, &failed);
146
147 if failed.is_empty() {
148 Ok(())
149 } else {
150 Err(ProcError::SignalError(format!(
151 "Failed to kill {} process(es)",
152 failed.len()
153 )))
154 }
155 }
156
157 fn print_confirmation_prompt(&self, processes: &[Process]) {
158 use colored::*;
159
160 println!(
161 "\n{} Found {} process{} to kill:\n",
162 "⚠".yellow().bold(),
163 processes.len().to_string().cyan().bold(),
164 if processes.len() == 1 { "" } else { "es" }
165 );
166
167 for proc in processes {
168 println!(
169 " {} {} [PID {}] - CPU: {:.1}%, MEM: {:.1}MB",
170 "→".bright_black(),
171 proc.name.white().bold(),
172 proc.pid.to_string().cyan(),
173 proc.cpu_percent,
174 proc.memory_mb
175 );
176 }
177 println!();
178 }
179}
180
181fn resolve_in_dir(in_dir: &Option<String>) -> Option<PathBuf> {
182 in_dir.as_ref().map(|p| {
183 if p == "." {
184 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
185 } else {
186 let path = PathBuf::from(p);
187 if path.is_relative() {
188 std::env::current_dir()
189 .unwrap_or_else(|_| PathBuf::from("."))
190 .join(path)
191 } else {
192 path
193 }
194 }
195 })
196}