#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::multiple_crate_versions)]
use colored::Colorize;
pub fn print_header(message: &str) {
println!("\n🌳 {}\n", message.bold());
}
pub fn print_repo_info(repo_root: &str) {
println!("Repository: {}", repo_root.cyan());
}
pub fn print_config_list(configs: &[(String, String)]) {
println!(
"Found {} config{}:",
configs.len(),
if configs.len() == 1 { "" } else { "s" }
);
for (path, description) in configs {
println!(" {} {} - {}", "•".dimmed(), path.yellow(), description);
}
println!();
}
pub fn print_profile_list(profiles: &[(String, String, usize)]) {
if profiles.is_empty() {
return;
}
println!(
"Available profile{}:",
if profiles.len() == 1 { "" } else { "s" }
);
for (name, description, config_count) in profiles {
let config_label = if *config_count == 1 {
"config".to_string()
} else {
format!("{config_count} configs")
};
if description.is_empty() {
println!(" {} {} ({})", "•".dimmed(), name.cyan(), config_label);
} else {
println!(
" {} {} - {} ({})",
"•".dimmed(),
name.cyan(),
description,
config_label
);
}
}
println!();
}
pub fn print_using_profile(names: &[String]) {
let label = if names.len() == 1 {
format!("Using profile: {}", names[0])
} else {
format!("Using profiles: {}", names.join(", "))
};
println!("{}", label.cyan());
}
pub fn print_profile_configs(configs: &[(String, String)]) {
println!(
"Using {} config{}:",
configs.len(),
if configs.len() == 1 { "" } else { "s" }
);
for (path, description) in configs {
println!(" {} {} - {}", "•".dimmed(), path.yellow(), description);
}
println!();
}
pub fn print_command(cmd: &str) {
println!(" {} {}", "$".dimmed(), cmd);
}
pub fn print_success() {
println!("✅ Worktree setup complete!");
}
pub fn print_error(message: &str) {
eprintln!("{} {}", "Error:".red().bold(), message);
}
pub fn print_warning(message: &str) {
println!("{} {}", "Warning:".yellow().bold(), message);
}
pub fn print_info(message: &str) {
println!("{} {}", "Info:".cyan().bold(), message);
}
#[derive(Clone)]
pub struct CleanItem {
pub relative_path: String,
pub is_dir: bool,
pub size: u64,
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn format_size(bytes: u64) -> String {
const KIB: u64 = 1024;
const MIB: u64 = 1024 * KIB;
const GIB: u64 = 1024 * MIB;
if bytes >= GIB {
format!("{:.1} GiB", bytes as f64 / GIB as f64)
} else if bytes >= MIB {
format!("{:.1} MiB", bytes as f64 / MIB as f64)
} else if bytes >= KIB {
format!("{:.1} KiB", bytes as f64 / KIB as f64)
} else {
format!("{bytes} B")
}
}
pub fn print_clean_preview(items: &[CleanItem]) {
if items.is_empty() {
println!("Nothing to clean.");
return;
}
println!(
"Will delete {} item{}:",
items.len(),
if items.len() == 1 { "" } else { "s" }
);
for item in items {
let type_label = if item.is_dir { "dir " } else { "file" };
let size_str = format_size(item.size);
println!(
" {} {} {} {}",
"•".dimmed(),
format!("[{type_label}]").dimmed(),
item.relative_path.yellow(),
format!("({size_str})").dimmed(),
);
}
let total_size: u64 = items.iter().map(|i| i.size).sum();
println!(
"\n {} {}",
"Total:".bold(),
format!("{} items, {}", items.len(), format_size(total_size)).bold()
);
}
pub fn print_clean_summary(deleted_count: usize, total_size: u64) {
println!(
"Deleted {} item{}, freed {}",
deleted_count,
if deleted_count == 1 { "" } else { "s" },
format_size(total_size)
);
}
pub fn print_multi_worktree_clean_preview(groups: &[(String, Vec<CleanItem>)]) {
let total_items: usize = groups.iter().map(|(_, items)| items.len()).sum();
if total_items == 0 {
println!("Nothing to clean across selected worktrees.");
return;
}
for (label, items) in groups {
if items.is_empty() {
continue;
}
println!(
" {} ({} item{}):",
label.cyan().bold(),
items.len(),
if items.len() == 1 { "" } else { "s" }
);
for item in items {
let type_label = if item.is_dir { "dir " } else { "file" };
let size_str = format_size(item.size);
println!(
" {} {} {} {}",
"•".dimmed(),
format!("[{type_label}]").dimmed(),
item.relative_path.yellow(),
format!("({size_str})").dimmed(),
);
}
println!();
}
let total_size: u64 = groups
.iter()
.flat_map(|(_, items)| items.iter().map(|i| i.size))
.sum();
let worktree_count = groups.iter().filter(|(_, items)| !items.is_empty()).count();
println!(
" {} {}",
"Total:".bold(),
format!(
"{total_items} items across {worktree_count} worktree{}, {}",
if worktree_count == 1 { "" } else { "s" },
format_size(total_size)
)
.bold()
);
}
pub fn print_multi_worktree_clean_summary(
deleted_count: usize,
total_size: u64,
worktree_count: usize,
) {
println!(
"Deleted {} item{} across {} worktree{}, freed {}",
deleted_count,
if deleted_count == 1 { "" } else { "s" },
worktree_count,
if worktree_count == 1 { "" } else { "s" },
format_size(total_size)
);
}
pub struct RemoveDisplayInfo {
pub branch: Option<String>,
pub path: String,
pub has_changes: bool,
}
pub fn print_remove_preview(worktrees: &[RemoveDisplayInfo]) {
if worktrees.is_empty() {
println!("No worktrees to remove.");
return;
}
println!(
"\nWill remove {} worktree{}:",
worktrees.len(),
if worktrees.len() == 1 { "" } else { "s" }
);
for wt in worktrees {
let label = wt.branch.as_deref().unwrap_or("(detached)");
let warning = if wt.has_changes {
format!(" {}", "(has uncommitted changes)".yellow())
} else {
String::new()
};
println!(
" {} {} {}{}",
"•".dimmed(),
label.cyan(),
wt.path.dimmed(),
warning,
);
}
println!();
}
pub fn print_remove_summary(removed: usize, failed: usize) {
if failed == 0 {
println!(
"{}",
format!(
"Removed {} worktree{}.",
removed,
if removed == 1 { "" } else { "s" }
)
.green()
);
} else {
println!(
"Removed {} worktree{}, {} failed.",
removed,
if removed == 1 { "" } else { "s" },
failed,
);
}
}
pub fn print_branch_delete_summary(deleted: &[String]) {
if deleted.is_empty() {
return;
}
println!(
"Deleted {} branch{}:",
deleted.len(),
if deleted.len() == 1 { "" } else { "es" }
);
for branch in deleted {
println!(" {} {}", "•".dimmed(), branch.cyan());
}
}
pub fn print_cwd_removed_note() {
println!(
"\n{} Your current directory was inside the removed worktree. Run {} to return to a valid directory.",
"Note:".yellow().bold(),
"cd ..".bold(),
);
}