use crate::file::FileInfo;
use crate::icons::icons;
use crate::port::PortInfo;
use anyhow::Result;
use colored::*;
use inquire::{Confirm, MultiSelect};
pub fn display_port_not_found(port: u16) {
println!("{}", format!("端口 {port} 未被占用").yellow());
}
pub fn select_processes_to_kill(port_infos: Vec<PortInfo>) -> Result<Vec<PortInfo>> {
if port_infos.is_empty() {
println!("{}", "未找到任何占用指定端口的进程".yellow());
return Ok(vec![]);
}
let options: Vec<String> = port_infos
.iter()
.map(|info| {
format!(
"端口 {} - {} (PID: {}) - {}",
info.port,
info.process.name,
info.process.pid,
info.process.cmd.join(" ")
)
})
.collect();
let defaults: Vec<usize> = (0..options.len()).collect();
let selected = MultiSelect::new("选择要终止的进程:", options)
.with_default(&defaults)
.prompt()?;
let mut result = Vec::new();
for selection in selected {
for info in &port_infos {
let expected = format!(
"端口 {} - {} (PID: {}) - {}",
info.port,
info.process.name,
info.process.pid,
info.process.cmd.join(" ")
);
if selection == expected {
result.push(info.clone());
break;
}
}
}
if result.is_empty() {
println!("{}", "未选择任何进程".yellow());
return Ok(vec![]);
}
let confirm = Confirm::new("确认终止这些进程?")
.with_default(false)
.prompt()?;
if confirm {
Ok(result)
} else {
println!("{}", "操作已取消".yellow());
Ok(vec![])
}
}
pub fn display_kill_results(results: &[(u32, Result<()>)]) {
for (pid, result) in results {
match result {
Ok(()) => println!(
"{} {}",
icons().check().green(),
format!("成功终止进程 {pid}").green()
),
Err(e) => println!(
"{} {}: {}",
icons().cross().red(),
format!("无法终止进程 {pid}").red(),
e
),
}
}
}
fn truncate_string(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
format!("{}...", &s[..max_len.saturating_sub(3)])
}
}
pub fn display_error(error: &anyhow::Error) {
eprintln!("{} {}", "错误:".red().bold(), error);
}
pub fn display_ports_tree(ports: &[u16], port_infos: Vec<PortInfo>) {
if ports.is_empty() {
return;
}
println!(
"{} {}",
icons().lightning().cyan(),
"端口查询结果".cyan().bold()
);
println!();
let mut port_map = std::collections::HashMap::new();
for info in port_infos {
port_map.insert(info.port, info);
}
let total = ports.len();
for (index, &port) in ports.iter().enumerate() {
let is_last = index == total - 1;
let branch = if is_last { "└─" } else { "├─" };
let continuation = if is_last { " " } else { "│ " };
if let Some(info) = port_map.get(&port) {
println!(
"{} {} {}",
branch,
format!("{port}").yellow().bold(),
icons().check().green()
);
println!(
"{}├─ {}: {} ({})",
continuation,
"进程".cyan(),
info.process.name.green(),
format!("{}", info.process.pid).bright_black()
);
let cmd = truncate_string(&info.process.cmd.join(" "), 60);
println!(
"{}├─ {}: {}",
continuation,
"命令".cyan(),
cmd.bright_black()
);
println!(
"{}└─ {}: {} CPU, {} 内存",
continuation,
"资源".cyan(),
format!("{:.1}%", info.process.cpu_usage).magenta(),
format!("{} MB", info.process.memory / 1024 / 1024).magenta()
);
} else {
println!(
"{} {} {} {}",
branch,
format!("{port}").yellow().bold(),
icons().cross().red(),
"(空闲)".bright_black()
);
}
if !is_last {
println!("{continuation}");
}
}
}
pub fn display_ports_tree_all(port_infos: Vec<PortInfo>) {
if port_infos.is_empty() {
println!("{}", "当前没有端口被占用".yellow());
return;
}
println!(
"{} {} {}",
icons().lightning().cyan(),
"端口占用情况".cyan().bold(),
format!("(共 {} 个)", port_infos.len()).bright_black()
);
println!();
let total = port_infos.len();
for (index, info) in port_infos.iter().enumerate() {
let is_last = index == total - 1;
let branch = if is_last { "└─" } else { "├─" };
let continuation = if is_last { " " } else { "│ " };
println!(
"{} {} {}",
branch,
format!("{}", info.port).yellow().bold(),
icons().check().green()
);
println!(
"{}├─ {}: {} ({})",
continuation,
"进程".cyan(),
info.process.name.green(),
format!("{}", info.process.pid).bright_black()
);
let cmd = truncate_string(&info.process.cmd.join(" "), 60);
println!(
"{}├─ {}: {}",
continuation,
"命令".cyan(),
cmd.bright_black()
);
println!(
"{}└─ {}: {} CPU, {} 内存",
continuation,
"资源".cyan(),
format!("{:.1}%", info.process.cpu_usage).magenta(),
format!("{} MB", info.process.memory / 1024 / 1024).magenta()
);
if !is_last {
println!("{continuation}");
}
}
}
pub fn display_deletion_preview(files: &[FileInfo]) {
let total_size: u64 = files.iter().map(|f| f.size).sum();
let (file_count, dir_count) = files.iter().fold((0, 0), |(files, dirs), f| {
if f.is_dir {
(files, dirs + 1)
} else {
(files + 1, dirs)
}
});
println!(
"{} {} {} {}",
"统计:".cyan().bold(),
format!("{file_count} 个文件").green(),
format!("{dir_count} 个目录").blue(),
format!("总大小: {}", crate::file::format_size(total_size)).yellow()
);
println!();
let total = files.len().min(10); for file in files.iter().take(total) {
let icon = if file.is_dir {
icons().folder().to_string()
} else if file.is_symlink {
icons().link().to_string()
} else {
icons().file().to_string()
};
let size_str = if !file.is_dir && !file.is_symlink {
format!(" ({})", crate::file::format_size(file.size))
} else {
String::new()
};
let file_type = if file.is_dir {
"目录".blue()
} else if file.is_symlink {
"符号链接".magenta()
} else {
"文件".green()
};
println!(
" {} {} {}{}",
icon,
file.path.display(),
file_type,
size_str.bright_black()
);
}
if files.len() > 10 {
println!(" ... 还有 {} 个项目", files.len() - 10);
}
println!();
}
pub fn confirm_deletion(files: &[FileInfo], force: bool, dry_run: bool) -> Result<bool> {
if dry_run {
println!(
"{} {}",
icons().search().blue(),
"预览模式 - 不会实际删除文件".blue().bold()
);
display_deletion_preview(files);
return Ok(true);
}
if force {
return Ok(true);
}
println!(
"{} {}",
icons().warning().red(),
"即将删除以下内容".red().bold()
);
display_deletion_preview(files);
let confirm = Confirm::new("确认删除这些内容?此操作不可撤销!")
.with_default(false)
.with_help_message("使用 --force 参数可以跳过此确认")
.prompt()?;
Ok(confirm)
}
pub fn display_removal_results(
results: &[(std::path::PathBuf, Result<()>)],
dry_run: bool,
verbose: bool,
) {
let action = if dry_run { "预览删除" } else { "删除" };
let (success_count, error_count) =
results
.iter()
.fold((0, 0), |(success, error), (_, result)| {
if result.is_ok() {
(success + 1, error)
} else {
(success, error + 1)
}
});
if !verbose {
println!(
"{} {} {}",
"操作完成".cyan().bold(),
format!("成功: {success_count}").green(),
format!("失败: {error_count}").red()
);
if error_count > 0 {
for (path, result) in results {
if let Err(e) = result {
println!(
"{} {} {}",
icons().cross().red(),
format!("无法删除 {}", path.display()).red(),
e
);
}
}
}
return;
}
println!(
"{} {} {}",
"操作完成".cyan().bold(),
format!("成功: {success_count}").green(),
format!("失败: {error_count}").red()
);
for (path, result) in results {
match result {
Ok(()) => println!(
"{} {}",
icons().check().green(),
format!("{} {}", action, path.display()).bright_black()
),
Err(e) => println!(
"{} {} {}",
icons().cross().red(),
format!("无法删除 {}", path.display()).red(),
e
),
}
}
}
pub fn display_kill_results_force(port_infos: &[PortInfo], results: &[(u32, Result<()>)]) {
println!("{} {}", icons().fire().red(), "强制终止进程".red().bold());
println!();
println!("{}", "目标进程:".cyan().bold());
for info in port_infos {
println!(
" 端口 {} - {} (PID: {})",
info.port.to_string().yellow(),
info.process.name.green(),
format!("{}", info.process.pid).bright_black()
);
}
println!();
println!("{}", "终止结果:".cyan().bold());
let mut success_count = 0;
let mut error_count = 0;
for (pid, result) in results {
match result {
Ok(()) => {
success_count += 1;
println!(
"{} {}",
icons().check().green(),
format!("成功强制终止进程 {pid}").green()
);
}
Err(e) => {
error_count += 1;
println!(
"{} {}: {}",
icons().cross().red(),
format!("无法强制终止进程 {pid}").red(),
e
);
}
}
}
println!();
println!(
"{} {} {}",
"强制终止完成".cyan().bold(),
format!("成功: {success_count}").green(),
format!("失败: {error_count}").red()
);
}