use super::*;
#[cfg(test)]
pub(crate) fn load_entries(
dir: &Path,
show_hidden: bool,
ext_filter: &[String],
sort_mode: SortMode,
search_query: &str,
) -> Vec<FsEntry> {
try_load_entries(
crate::std_filesystem().as_ref(),
dir,
show_hidden,
ext_filter,
sort_mode,
search_query,
)
.unwrap_or_default()
}
pub(crate) fn try_load_entries(
filesystem: &dyn crate::FileSystem,
dir: &Path,
show_hidden: bool,
ext_filter: &[String],
sort_mode: SortMode,
search_query: &str,
) -> std::io::Result<Vec<FsEntry>> {
let read = filesystem.read_dir(dir)?;
let mut dirs: Vec<FsEntry> = Vec::new();
let mut files: Vec<FsEntry> = Vec::new();
for fs_entry in read {
let name = &fs_entry.name;
if !show_hidden && name.starts_with('.') {
continue;
}
let is_dir = fs_entry.is_dir;
let extension = &fs_entry.extension;
if !is_dir && !ext_filter.is_empty() {
let matches = ext_filter.iter().any(|f| f.eq_ignore_ascii_case(extension));
if !matches {
continue;
}
}
if !search_query.is_empty() {
let q = search_query.to_lowercase();
if !name.to_lowercase().contains(&q) {
continue;
}
}
if is_dir {
dirs.push(fs_entry);
} else {
files.push(fs_entry);
}
}
dirs.sort_by_key(|a| a.name.to_lowercase());
match sort_mode {
SortMode::Name => {
files.sort_by_key(|a| a.name.to_lowercase());
}
SortMode::SizeDesc => {
files.sort_by_key(|b| std::cmp::Reverse(b.size.unwrap_or(0)));
}
SortMode::Extension => {
files.sort_by(|a, b| {
a.extension
.cmp(&b.extension)
.then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase()))
});
}
}
dirs.extend(files);
Ok(dirs)
}
macro_rules! icon_map {
( dir => $dir_icon:expr, $( [ $( $ext:literal ),+ ] => $icon:expr, )* default => $fallback:expr $(,)? ) => {
pub fn entry_icon(entry: &FsEntry) -> &'static str {
if entry.is_dir {
return $dir_icon;
}
match entry.extension.as_str() {
$( $( $ext )|+ => $icon, )*
_ => $fallback,
}
}
};
}
icon_map! {
dir => "📁",
["iso", "dmg"] => "💿",
["img"] => "🖼 ",
["zip", "gz", "xz", "zst", "bz2", "tar", "7z", "rar", "tgz", "tbz2"] => "📦",
["pdf"] => "📕",
["txt", "log", "rst"] => "📄",
["md", "mdx", "markdown"] => "📝",
["toml", "yaml", "yml", "json", "xml", "ini", "cfg", "conf", "env"] => "⚙ ",
["lock"] => "🔒",
["rs"] => "🦀",
["py", "pyw"] => "🐍",
["js", "mjs", "cjs", "ts", "mts", "cts", "jsx", "tsx", "go",
"c", "h", "cpp", "cc", "cxx", "hpp", "hxx",
"java", "kt", "kts", "rb", "erb", "php", "swift", "cs",
"lua", "zig", "ex", "exs", "hs", "lhs", "ml", "mli"] => "📜",
["sh", "bash", "zsh", "fish", "nu", "bat", "cmd", "ps1"] => "📜",
["html", "htm", "xhtml"] => "🌐",
["css", "scss", "sass", "less", "svg"] => "🎨",
["png", "jpg", "jpeg", "gif", "bmp", "webp", "ico", "tiff", "tif", "avif", "heic", "heif"] => "🖼 ",
["mp4", "mkv", "avi", "mov", "webm", "flv", "wmv", "m4v"] => "🎬",
["mp3", "wav", "flac", "ogg", "aac", "m4a", "opus", "wma"] => "🎵",
["ttf", "otf", "woff", "woff2", "eot"] => "🔤",
["exe", "msi", "deb", "rpm", "appimage", "apk"] => "⚙ ",
default => "📄",
}
#[must_use]
pub fn fmt_size(bytes: u64) -> String {
const KB: u64 = 1_024;
const MB: u64 = 1_024 * KB;
const GB: u64 = 1_024 * MB;
if bytes >= GB {
format!("{:.1} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.1} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.1} KB", bytes as f64 / KB as f64)
} else {
format!("{bytes} B")
}
}