use ratatui::layout::{Constraint, Direction, Layout, Rect};
pub fn format_size(size: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
let mut size = size as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
if unit_index == 0 {
format!("{} {}", size as u64, UNITS[unit_index])
} else {
format!("{:.1} {}", size, UNITS[unit_index])
}
}
pub fn get_extension_icon(ext: &str) -> &'static str {
match ext {
"rs" => "🦀",
"py" => "🐍",
"js" | "jsx" => "📜",
"ts" | "tsx" => "📘",
"html" => "🌐",
"css" | "scss" | "sass" => "🎨",
"json" => "📋",
"xml" => "📄",
"yaml" | "yml" => "⚙️",
"toml" => "🔧",
"md" => "📝",
"txt" => "📄",
"java" => "☕",
"c" | "cpp" | "cc" | "cxx" => "⚡",
"h" | "hpp" => "📎",
"go" => "🐹",
"php" => "🐘",
"rb" => "💎",
"swift" => "🍎",
"kt" => "🎯",
"scala" => "🎭",
"sh" | "bash" | "zsh" => "🐚",
_ => "📄",
}
}
pub fn get_file_icon(file_path: &str) -> &'static str {
if file_path.ends_with(".rs") {
"🦀"
} else if file_path.ends_with(".py") {
"🐍"
} else if file_path.ends_with(".js") || file_path.ends_with(".jsx") {
"📜"
} else if file_path.ends_with(".ts") || file_path.ends_with(".tsx") {
"📘"
} else if file_path.ends_with(".toml") {
"🔧"
} else if file_path.ends_with(".json") {
"📋"
} else if file_path.ends_with(".md") {
"📝"
} else {
"📄"
}
}
#[derive(Debug, Clone)]
pub struct LanguageInfo {
pub name: String,
pub icon: String,
pub color: String,
pub extensions: Vec<String>,
}
pub fn get_language_from_extension(ext: &str) -> LanguageInfo {
get_language_from_extension_with_sherlock(ext, None)
}
pub fn get_language_from_extension_with_sherlock(
ext: &str,
sherlock_result: Option<&crate::core::detector::SherlockResult>,
) -> LanguageInfo {
if let Some(sherlock) = sherlock_result {
for language in &sherlock.languages {
for file in &language.files {
if let Some(file_ext) = std::path::Path::new(file).extension() {
if file_ext.to_string_lossy().to_lowercase() == ext.to_lowercase() {
return LanguageInfo {
name: language.name.clone(),
icon: get_language_icon(&language.name),
color: language.color.clone(),
extensions: vec![ext.to_string()],
};
}
}
}
}
}
get_language_from_extension_fallback(ext)
}
fn get_language_icon(language_name: &str) -> String {
match language_name.to_lowercase().as_str() {
"rust" => "🦀".to_string(),
"python" => "🐍".to_string(),
"javascript" => "📜".to_string(),
"typescript" => "📘".to_string(),
"java" => "☕".to_string(),
"c++" => "⚡".to_string(),
"c" => "🔧".to_string(),
"go" => "🐹".to_string(),
"ruby" => "💎".to_string(),
"php" => "🐘".to_string(),
"c#" => "🔷".to_string(),
"swift" => "🍎".to_string(),
"kotlin" => "🎯".to_string(),
"html" => "🌐".to_string(),
"css" => "🎨".to_string(),
"sass" | "scss" => "🎨".to_string(),
"json" => "📋".to_string(),
"yaml" => "⚙️".to_string(),
"toml" => "🔧".to_string(),
"markdown" => "📝".to_string(),
"shell" => "🐚".to_string(),
_ => "📄".to_string(),
}
}
fn get_language_from_extension_fallback(ext: &str) -> LanguageInfo {
match ext {
"rs" => LanguageInfo {
name: "Rust".to_string(),
icon: "🦀".to_string(),
color: "#dea584".to_string(),
extensions: vec!["rs".to_string()],
},
"py" => LanguageInfo {
name: "Python".to_string(),
icon: "🐍".to_string(),
color: "#3776ab".to_string(),
extensions: vec!["py".to_string()],
},
"js" => LanguageInfo {
name: "JavaScript".to_string(),
icon: "📜".to_string(),
color: "#f7df1e".to_string(),
extensions: vec!["js".to_string()],
},
"jsx" => LanguageInfo {
name: "React JSX".to_string(),
icon: "⚛️".to_string(),
color: "#61dafb".to_string(),
extensions: vec!["jsx".to_string()],
},
"ts" => LanguageInfo {
name: "TypeScript".to_string(),
icon: "📘".to_string(),
color: "#3178c6".to_string(),
extensions: vec!["ts".to_string()],
},
"tsx" => LanguageInfo {
name: "React TSX".to_string(),
icon: "⚛️".to_string(),
color: "#61dafb".to_string(),
extensions: vec!["tsx".to_string()],
},
"html" => LanguageInfo {
name: "HTML".to_string(),
icon: "🌐".to_string(),
color: "#e34f26".to_string(),
extensions: vec!["html".to_string()],
},
"css" => LanguageInfo {
name: "CSS".to_string(),
icon: "🎨".to_string(),
color: "#1572b6".to_string(),
extensions: vec!["css".to_string()],
},
"scss" | "sass" => LanguageInfo {
name: "Sass".to_string(),
icon: "🎨".to_string(),
color: "#cf649a".to_string(),
extensions: vec!["scss".to_string(), "sass".to_string()],
},
"java" => LanguageInfo {
name: "Java".to_string(),
icon: "☕".to_string(),
color: "#ed8b00".to_string(),
extensions: vec!["java".to_string()],
},
"c" => LanguageInfo {
name: "C".to_string(),
icon: "⚡".to_string(),
color: "#00599c".to_string(),
extensions: vec!["c".to_string()],
},
"cpp" | "cc" | "cxx" => LanguageInfo {
name: "C++".to_string(),
icon: "⚡".to_string(),
color: "#00599c".to_string(),
extensions: vec!["cpp".to_string(), "cc".to_string(), "cxx".to_string()],
},
"h" | "hpp" => LanguageInfo {
name: "C/C++ Header".to_string(),
icon: "📎".to_string(),
color: "#00599c".to_string(),
extensions: vec!["h".to_string(), "hpp".to_string()],
},
"go" => LanguageInfo {
name: "Go".to_string(),
icon: "🐹".to_string(),
color: "#00add8".to_string(),
extensions: vec!["go".to_string()],
},
"php" => LanguageInfo {
name: "PHP".to_string(),
icon: "🐘".to_string(),
color: "#777bb4".to_string(),
extensions: vec!["php".to_string()],
},
"rb" => LanguageInfo {
name: "Ruby".to_string(),
icon: "💎".to_string(),
color: "#cc342d".to_string(),
extensions: vec!["rb".to_string()],
},
"swift" => LanguageInfo {
name: "Swift".to_string(),
icon: "🍎".to_string(),
color: "#fa7343".to_string(),
extensions: vec!["swift".to_string()],
},
"kt" => LanguageInfo {
name: "Kotlin".to_string(),
icon: "🎯".to_string(),
color: "#7f52ff".to_string(),
extensions: vec!["kt".to_string()],
},
"scala" => LanguageInfo {
name: "Scala".to_string(),
icon: "🎭".to_string(),
color: "#dc322f".to_string(),
extensions: vec!["scala".to_string()],
},
"cs" => LanguageInfo {
name: "C#".to_string(),
icon: "🔷".to_string(),
color: "#239120".to_string(),
extensions: vec!["cs".to_string()],
},
"sh" | "bash" | "zsh" => LanguageInfo {
name: "Shell".to_string(),
icon: "🐚".to_string(),
color: "#89e051".to_string(),
extensions: vec!["sh".to_string(), "bash".to_string(), "zsh".to_string()],
},
"json" => LanguageInfo {
name: "JSON".to_string(),
icon: "📋".to_string(),
color: "#000000".to_string(),
extensions: vec!["json".to_string()],
},
"xml" => LanguageInfo {
name: "XML".to_string(),
icon: "📄".to_string(),
color: "#e37933".to_string(),
extensions: vec!["xml".to_string()],
},
"yaml" | "yml" => LanguageInfo {
name: "YAML".to_string(),
icon: "⚙️".to_string(),
color: "#cb171e".to_string(),
extensions: vec!["yaml".to_string(), "yml".to_string()],
},
"toml" => LanguageInfo {
name: "TOML".to_string(),
icon: "🔧".to_string(),
color: "#9c4221".to_string(),
extensions: vec!["toml".to_string()],
},
"md" => LanguageInfo {
name: "Markdown".to_string(),
icon: "📝".to_string(),
color: "#083fa1".to_string(),
extensions: vec!["md".to_string()],
},
"txt" => LanguageInfo {
name: "Text".to_string(),
icon: "📄".to_string(),
color: "#6c757d".to_string(),
extensions: vec!["txt".to_string()],
},
"sql" => LanguageInfo {
name: "SQL".to_string(),
icon: "🗃️".to_string(),
color: "#e38c00".to_string(),
extensions: vec!["sql".to_string()],
},
"r" => LanguageInfo {
name: "R".to_string(),
icon: "📊".to_string(),
color: "#198ce7".to_string(),
extensions: vec!["r".to_string()],
},
"dart" => LanguageInfo {
name: "Dart".to_string(),
icon: "🎯".to_string(),
color: "#0175c2".to_string(),
extensions: vec!["dart".to_string()],
},
"hs" | "lhs" | "hsc" => LanguageInfo {
name: "Haskell".to_string(),
icon: "λ".to_string(),
color: "#5e5086".to_string(),
extensions: vec!["hs".to_string(), "lhs".to_string(), "hsc".to_string()],
},
"ex" | "exs" | "eex" => LanguageInfo {
name: "Elixir".to_string(),
icon: "💧".to_string(),
color: "#6e4a7e".to_string(),
extensions: vec!["ex".to_string(), "exs".to_string(), "eex".to_string()],
},
"erl" | "hrl" => LanguageInfo {
name: "Erlang".to_string(),
icon: "📞".to_string(),
color: "#b83998".to_string(),
extensions: vec!["erl".to_string(), "hrl".to_string()],
},
"jl" => LanguageInfo {
name: "Julia".to_string(),
icon: "🔬".to_string(),
color: "#9558b2".to_string(),
extensions: vec!["jl".to_string()],
},
"lua" => LanguageInfo {
name: "Lua".to_string(),
icon: "🌙".to_string(),
color: "#000080".to_string(),
extensions: vec!["lua".to_string()],
},
"pl" | "pm" | "pod" => LanguageInfo {
name: "Perl".to_string(),
icon: "🐪".to_string(),
color: "#0298c3".to_string(),
extensions: vec!["pl".to_string(), "pm".to_string(), "pod".to_string()],
},
"m" => LanguageInfo {
name: "MATLAB".to_string(),
icon: "📊".to_string(),
color: "#e16737".to_string(),
extensions: vec!["m".to_string()],
},
"zig" => LanguageInfo {
name: "Zig".to_string(),
icon: "⚡".to_string(),
color: "#ec915c".to_string(),
extensions: vec!["zig".to_string()],
},
"clj" | "cljs" | "cljc" => LanguageInfo {
name: "Clojure".to_string(),
icon: "🔄".to_string(),
color: "#db5855".to_string(),
extensions: vec!["clj".to_string(), "cljs".to_string(), "cljc".to_string()],
},
"ps1" | "psm1" | "psd1" => LanguageInfo {
name: "PowerShell".to_string(),
icon: "⚡".to_string(),
color: "#012456".to_string(),
extensions: vec!["ps1".to_string(), "psm1".to_string(), "psd1".to_string()],
},
"bat" | "cmd" => LanguageInfo {
name: "Batch".to_string(),
icon: "⚙️".to_string(),
color: "#c1f12e".to_string(),
extensions: vec!["bat".to_string(), "cmd".to_string()],
},
"vb" | "vbs" => LanguageInfo {
name: "Visual Basic".to_string(),
icon: "🔷".to_string(),
color: "#945db7".to_string(),
extensions: vec!["vb".to_string(), "vbs".to_string()],
},
"mlx" => LanguageInfo {
name: "MATLAB".to_string(),
icon: "📊".to_string(),
color: "#e16737".to_string(),
extensions: vec!["m".to_string(), "mlx".to_string()],
},
"rmd" | "Rmd" => LanguageInfo {
name: "R Markdown".to_string(),
icon: "📊".to_string(),
color: "#198ce7".to_string(),
extensions: vec![
"r".to_string(),
"R".to_string(),
"rmd".to_string(),
"Rmd".to_string(),
],
},
_ => LanguageInfo {
name: "Unknown".to_string(),
icon: "📄".to_string(),
color: "#6c757d".to_string(),
extensions: vec![ext.to_string()],
},
}
}
pub fn group_extensions_by_language(
stats_by_extension: &std::collections::BTreeMap<String, (usize, crate::core::types::FileStats)>,
) -> std::collections::BTreeMap<String, (LanguageInfo, usize, crate::core::types::FileStats)> {
let mut language_stats: std::collections::BTreeMap<
String,
(LanguageInfo, usize, crate::core::types::FileStats),
> = std::collections::BTreeMap::new();
for (ext, (file_count, file_stats)) in stats_by_extension {
let language_info = get_language_from_extension(ext);
let language_name = language_info.name.clone();
if let Some((existing_info, existing_count, existing_stats)) =
language_stats.get_mut(&language_name)
{
*existing_count += file_count;
existing_stats.total_lines += file_stats.total_lines;
existing_stats.code_lines += file_stats.code_lines;
existing_stats.comment_lines += file_stats.comment_lines;
existing_stats.doc_lines += file_stats.doc_lines;
existing_stats.blank_lines += file_stats.blank_lines;
existing_stats.file_size += file_stats.file_size;
if !existing_info.extensions.contains(&ext.to_string()) {
existing_info.extensions.push(ext.to_string());
}
} else {
language_stats.insert(
language_name,
(language_info, *file_count, file_stats.clone()),
);
}
}
language_stats
}
pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(r);
Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(popup_layout[1])[1]
}
pub fn shorten_path(path: &str, max_width: usize) -> String {
if path.len() <= max_width {
path.to_string()
} else {
let parts: Vec<&str> = path.split('/').collect();
if parts.len() <= 2 {
format!("...{}", &path[path.len() - max_width + 3..])
} else {
let filename = parts.last().map_or("", |v| v);
let first_part = parts.first().map_or("", |v| v);
let remaining_width = max_width - 3 - filename.len() - first_part.len();
if remaining_width > 0 {
format!("{}/.../{}", first_part, filename)
} else {
format!(".../{}", filename)
}
}
}
}