use crate::file::FileInfo;
use crate::port::PortInfo;
use crate::theme::Theme;
use crate::top::ProcessView;
use anyhow::Result;
use console::{Alignment, pad_str};
use inquire::{Confirm, MultiSelect};
use std::io::{self, Write};
pub fn display_port_not_found(port: u16) {
let theme = Theme::new();
println!("{}", theme.warn(format!("端口 {port} 未被占用")));
}
pub fn select_processes_to_kill(port_infos: Vec<PortInfo>) -> Result<Vec<PortInfo>> {
let theme = Theme::new();
if port_infos.is_empty() {
println!("{}", theme.warn("未找到任何占用指定端口的进程"));
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!("{}", theme.warn("未选择任何进程"));
return Ok(vec![]);
}
let confirm = Confirm::new("确认终止这些进程?")
.with_default(false)
.prompt()?;
if confirm {
Ok(result)
} else {
println!("{}", theme.warn("操作已取消"));
Ok(vec![])
}
}
pub fn display_kill_results(results: &[(u32, Result<()>)]) {
let theme = Theme::new();
for (pid, result) in results {
match result {
Ok(()) => println!(
"{} {}",
theme.icon_success(),
theme.success(format!("成功终止进程 {pid}"))
),
Err(e) => println!(
"{} {}: {}",
theme.icon_error(),
theme.error(format!("无法终止进程 {pid}")),
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) {
let theme = Theme::new();
eprintln!("{} {}", theme.error_bold("错误:"), error);
}
pub fn display_ports_tree(ports: &[u16], port_infos: Vec<PortInfo>) {
if ports.is_empty() {
return;
}
let theme = Theme::new();
println!("{} {}", theme.icon_lightning(), theme.title("端口查询结果"));
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,
theme.highlight(port.to_string()),
theme.icon_success()
);
println!(
"{}├─ {}: {} ({})",
continuation,
theme.info("进程"),
theme.success(&info.process.name),
theme.muted(info.process.pid.to_string())
);
let cmd = truncate_string(&info.process.cmd.join(" "), 60);
println!(
"{}├─ {}: {}",
continuation,
theme.info("命令"),
theme.muted(cmd)
);
println!(
"{}└─ {}: {} CPU, {} 内存",
continuation,
theme.info("资源"),
theme.accent(format!("{:.1}%", info.process.cpu_usage)),
theme.accent(format!("{} MB", info.process.memory / 1024 / 1024))
);
} else {
println!(
"{} {} {} {}",
branch,
theme.highlight(port.to_string()),
theme.icon_error(),
theme.muted("(空闲)")
);
}
if !is_last {
println!("{continuation}");
}
}
}
pub fn display_ports_tree_all(port_infos: Vec<PortInfo>) {
let theme = Theme::new();
if port_infos.is_empty() {
println!("{}", theme.warn("当前没有端口被占用"));
return;
}
println!(
"{} {} {}",
theme.icon_lightning(),
theme.title("端口占用情况"),
theme.muted(format!("(共 {} 个)", port_infos.len()))
);
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,
theme.highlight(info.port.to_string()),
theme.icon_success()
);
println!(
"{}├─ {}: {} ({})",
continuation,
theme.info("进程"),
theme.success(&info.process.name),
theme.muted(info.process.pid.to_string())
);
let cmd = truncate_string(&info.process.cmd.join(" "), 60);
println!(
"{}├─ {}: {}",
continuation,
theme.info("命令"),
theme.muted(cmd)
);
println!(
"{}└─ {}: {} CPU, {} 内存",
continuation,
theme.info("资源"),
theme.accent(format!("{:.1}%", info.process.cpu_usage)),
theme.accent(format!("{} MB", info.process.memory / 1024 / 1024))
);
if !is_last {
println!("{continuation}");
}
}
}
pub fn display_deletion_preview(files: &[FileInfo]) {
let theme = Theme::new();
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!(
"{} {} {} {}",
theme.title("统计:"),
theme.success(format!("{file_count} 个文件")),
theme.blue(format!("{dir_count} 个目录")),
theme.warn(format!("总大小: {}", crate::file::format_size(total_size)))
);
println!();
let total = files.len().min(10); for file in files.iter().take(total) {
let icon = if file.is_dir {
theme.icon_folder()
} else if file.is_symlink {
theme.icon_link()
} else {
theme.icon_file()
};
let size_str = if !file.is_dir && !file.is_symlink {
let size = format!(" ({})", crate::file::format_size(file.size));
theme.muted(size)
} else {
String::new()
};
let file_type = if file.is_dir {
theme.blue("目录")
} else if file.is_symlink {
theme.accent("符号链接")
} else {
theme.success("文件")
};
println!(
" {} {} {}{}",
icon,
file.path.display(),
file_type,
size_str
);
}
if files.len() > 10 {
println!(
"{}",
theme.muted(format!(" ... 还有 {} 个项目", files.len() - 10))
);
}
println!();
}
pub fn confirm_deletion(files: &[FileInfo], force: bool, dry_run: bool) -> Result<bool> {
let theme = Theme::new();
if dry_run {
println!(
"{} {}",
theme.icon_search(),
theme.info_bold("预览模式 - 不会实际删除文件")
);
display_deletion_preview(files);
return Ok(true);
}
if force {
return Ok(true);
}
println!(
"{} {}",
theme.icon_warning(),
theme.error_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 theme = Theme::new();
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!(
"{} {} {}",
theme.title("操作完成"),
theme.success(format!("成功: {success_count}")),
theme.error(format!("失败: {error_count}"))
);
if error_count > 0 {
for (path, result) in results {
if let Err(e) = result {
println!(
"{} {} {}",
theme.icon_error(),
theme.error(format!("无法删除 {}", path.display())),
e
);
}
}
}
return;
}
println!(
"{} {} {}",
theme.title("操作完成"),
theme.success(format!("成功: {success_count}")),
theme.error(format!("失败: {error_count}"))
);
for (path, result) in results {
match result {
Ok(()) => println!(
"{} {}",
theme.icon_success(),
theme.muted(format!("{} {}", action, path.display()))
),
Err(e) => println!(
"{} {} {}",
theme.icon_error(),
theme.error(format!("无法删除 {}", path.display())),
e
),
}
}
}
pub fn display_kill_results_force(port_infos: &[PortInfo], results: &[(u32, Result<()>)]) {
let theme = Theme::new();
println!("{} {}", theme.icon_fire(), theme.error_bold("强制终止进程"));
println!();
println!("{}", theme.title("目标进程:"));
for info in port_infos {
println!(
" 端口 {} - {} (PID: {})",
theme.highlight(info.port.to_string()),
theme.success(&info.process.name),
theme.muted(info.process.pid.to_string())
);
}
println!();
println!("{}", theme.title("终止结果:"));
let mut success_count = 0;
let mut error_count = 0;
for (pid, result) in results {
match result {
Ok(()) => {
success_count += 1;
println!(
"{} {}",
theme.icon_success(),
theme.success(format!("成功强制终止进程 {pid}"))
);
}
Err(e) => {
error_count += 1;
println!(
"{} {}: {}",
theme.icon_error(),
theme.error(format!("无法强制终止进程 {pid}")),
e
);
}
}
}
println!();
println!(
"{} {} {}",
theme.title("强制终止完成"),
theme.success(format!("成功: {success_count}")),
theme.error(format!("失败: {error_count}"))
);
}
pub fn display_top(
processes: &[ProcessView],
refresh: u64,
interval: f32,
show_cpu: bool,
show_cmd: bool,
) {
let theme = Theme::new();
const RANK_W: usize = 4;
const NAME_W: usize = 26;
const PID_W: usize = 10;
const MEM_W: usize = 10;
const CPU_W: usize = 8;
print!("\x1b[2J\x1b[H");
let _ = io::stdout().flush();
println!("{} {}", theme.icon_lightning(), theme.title("进程内存占用"));
println!(
"{}",
theme.muted(format!(
"刷新次数: {} | 间隔: {:.1}s | 显示前 {} | Ctrl+C 退出",
refresh,
interval,
processes.len()
))
);
println!();
let header_rank = pad_str("序号", RANK_W, Alignment::Left, None);
let header_name = pad_str("名称", NAME_W, Alignment::Left, None);
let header_pid = pad_str("PID", PID_W, Alignment::Left, None);
let header_mem = pad_str("内存", MEM_W, Alignment::Right, None);
let header_cpu = pad_str("CPU", CPU_W, Alignment::Right, None);
let header_cmd = if show_cmd { "命令" } else { "" };
println!("{header_rank} {header_name} {header_pid} {header_mem} {header_cpu} {header_cmd}");
let sep_len = RANK_W + NAME_W + PID_W + MEM_W + CPU_W + 5; println!("{}", theme.muted("—".repeat(sep_len)));
for (index, process) in processes.iter().enumerate() {
let rank = index + 1;
let rank_str = match rank {
1 => theme.highlight(rank.to_string()),
2 => theme.warn(rank.to_string()),
3 => theme.info(rank.to_string()),
_ => theme.muted(rank.to_string()),
};
let mem_str = crate::file::format_size(process.memory_bytes);
let cpu_str = if show_cpu {
format!("{:.1}%", process.cpu)
} else {
"-".to_string()
};
let name = truncate_string(&process.name, NAME_W.saturating_sub(2));
let pid_str = theme.muted(process.pid.to_string());
let cmd_display = if show_cmd && !process.cmd.is_empty() {
format!(" {}", theme.muted(truncate_string(&process.cmd, 60)))
} else {
String::new()
};
let name_styled = theme.success(name);
let mem_styled = theme.warn(mem_str.clone());
let cpu_styled = theme.accent(cpu_str.clone());
let name_cell = pad_str(&name_styled, NAME_W, Alignment::Left, None);
let pid_cell = pad_str(&pid_str, PID_W, Alignment::Left, None);
let mem_cell = pad_str(&mem_styled, MEM_W, Alignment::Right, None);
let cpu_cell = pad_str(&cpu_styled, CPU_W, Alignment::Right, None);
println!(
"{} {} {} {} {}{}",
pad_str(&rank_str, RANK_W, Alignment::Left, None),
name_cell,
pid_cell,
mem_cell,
cpu_cell,
cmd_display
);
}
}