use std::cmp::Ordering;
use crate::icons::file_kind;
use super::details::FileDetails;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
#[non_exhaustive]
pub enum SortBy {
#[default]
Name,
Size,
Changed,
Kind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct Sort {
pub by: SortBy,
pub reverse: bool,
}
impl Sort {
#[must_use]
pub fn by(by: SortBy) -> Self {
Self { by, reverse: false }
}
#[must_use]
pub fn reversed(self, reverse: bool) -> Self {
Self { reverse, ..self }
}
pub(super) fn needs_details(self) -> bool {
matches!(self.by, SortBy::Size | SortBy::Changed)
}
}
pub(super) struct Ranked<'a> {
pub(super) name: &'a str,
pub(super) folder: bool,
pub(super) executable: bool,
pub(super) details: Option<&'a FileDetails>,
pub(super) changed: Option<i64>,
}
pub(super) fn compare(sort: Sort, a: &Ranked<'_>, b: &Ranked<'_>) -> Ordering {
let by_name = || a.name.cmp(b.name);
let turned = |order: Ordering| if sort.reverse { order.reverse() } else { order };
let known = |order: Option<Ordering>, a_known: bool, b_known: bool| match (a_known, b_known) {
(true, true) => turned(order.unwrap_or(Ordering::Equal)),
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
(false, false) => Ordering::Equal,
};
let changed = |entry: &Ranked<'_>| entry.changed.or_else(|| entry.details.and_then(|details| details.modified));
let order = match sort.by {
SortBy::Name => turned(by_name()),
SortBy::Size => {
let (x, y) = (a.details.map(|d| d.size), b.details.map(|d| d.size));
known(x.zip(y).map(|(x, y)| x.cmp(&y)), x.is_some(), y.is_some())
}
SortBy::Changed => {
let (x, y) = (changed(a), changed(b));
known(x.zip(y).map(|(x, y)| x.cmp(&y)), x.is_some(), y.is_some())
}
SortBy::Kind => {
let kind = |entry: &Ranked<'_>| file_kind(entry.name, entry.folder, entry.executable).icon();
turned(kind(a).cmp(kind(b)))
}
};
b.folder.cmp(&a.folder).then(order).then_with(by_name)
}