fm/modes/menu/
sort.rs

1use std::cmp::Ordering;
2
3use crate::modes::FileInfo;
4
5/// Different kind of sort
6#[derive(Debug, Clone, Default, Copy)]
7enum SortBy {
8    #[default]
9    /// Directory first
10    Kind,
11    /// by filename
12    File,
13    /// by date
14    Date,
15    /// by size
16    Size,
17    /// by extension
18    Exte,
19}
20
21impl std::fmt::Display for SortBy {
22    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
23        let sort_by = match &self {
24            Self::Exte => "Exte",
25            Self::Date => "Date",
26            Self::File => "File",
27            Self::Size => "Size",
28            Self::Kind => "Kind",
29        };
30        write!(f, "{sort_by}")
31    }
32}
33
34/// Ascending or descending sort
35#[derive(Debug, Clone, Default, Copy)]
36enum Order {
37    #[default]
38    /// Ascending order
39    Ascending,
40    /// Descending order
41    Descending,
42}
43
44impl Order {
45    const fn reverse(self) -> Self {
46        match self {
47            Self::Descending => Self::Ascending,
48            Self::Ascending => Self::Descending,
49        }
50    }
51}
52
53#[derive(Debug, Clone, Default, Copy)]
54/// Describe a way of sorting
55pub struct SortKind {
56    /// The key used to sort the files
57    sort_by: SortBy,
58    /// Ascending or descending order
59    order: Order,
60}
61
62impl SortKind {
63    /// Default kind of sort for a tree view.
64    /// Since files are in reverse order, we need to sort in descending order.
65    #[must_use]
66    pub const fn tree_default() -> Self {
67        Self {
68            sort_by: SortBy::Kind,
69            order: Order::Descending,
70        }
71    }
72
73    /// Updates itself from a given character.
74    /// If the character describes a kind of sort, we apply it. (k n m s e -- K N M S E)
75    /// If the character is lowercase, we sort by Ascending order, else Descending order.
76    /// If the character is 'r' or 'R' we reverse current kind of sort.
77    pub fn update_from_char(&mut self, c: char) {
78        match c.to_ascii_uppercase() {
79            'K' => self.sort_by = SortBy::Kind,
80            'N' => self.sort_by = SortBy::File,
81            'M' => self.sort_by = SortBy::Date,
82            'S' => self.sort_by = SortBy::Size,
83            'E' => self.sort_by = SortBy::Exte,
84            'R' => self.order = self.order.reverse(),
85            _ => {
86                return;
87            }
88        }
89        if c != 'r' {
90            if c.is_uppercase() {
91                self.order = Order::Descending;
92            } else {
93                self.order = Order::Ascending;
94            }
95        }
96    }
97    /// Use Higher Rank Trait Bounds
98    /// Avoid using slices to sort a collection.
99    /// It allows use to use references to `String` (`&str`) instead of cloning the `String`.
100    /// Reference: [StackOverflow](https://stackoverflow.com/questions/56105305/how-to-sort-a-vec-of-structs-by-a-string-field)
101    fn sort_by_key_hrtb<T, F, K>(slice: &mut [T], f: F)
102    where
103        F: for<'a> Fn(&'a T) -> &'a K,
104        K: Ord + ?Sized,
105    {
106        slice.sort_unstable_by(|a, b| f(a).cmp(f(b)));
107    }
108
109    /// Use Higher Rank Trait Bounds
110    /// Avoid using slices to sort a collection.
111    /// It allows use to use references to `String` (`&str`) instead of cloning the `String`.
112    /// Reference: [StackOverflow](https://stackoverflow.com/questions/56105305/how-to-sort-a-vec-of-structs-by-a-string-field)
113    /// This version uses a reversed comparaison, allowing a descending sort.
114    fn reversed_sort_by_key_hrtb<T, F, K>(slice: &mut [T], f: F)
115    where
116        F: for<'a> Fn(&'a T) -> &'a K,
117        K: Ord + ?Sized,
118    {
119        slice.sort_unstable_by(|a, b| Ordering::reverse(f(a).cmp(f(b))));
120    }
121
122    // A second version should take 2 parameters.
123    // 1. the way to access the data depending on T where files: &mut [T],
124    // 2. a closure returning the correct data.
125
126    /// Sort a collection of file depending of enum variants.
127    #[rustfmt::skip]
128    pub fn sort(&self, files: &mut [FileInfo]) {
129        if matches!(self.order, Order::Ascending) {
130            match self.sort_by {
131                SortBy::Kind => files.sort_unstable_by(|a, b| natord::compare(&a.kind_format(), &b.kind_format())),
132                SortBy::File => files.sort_unstable_by(|a, b| natord::compare(&a.filename, &b.filename)),
133                SortBy::Date => Self::sort_by_key_hrtb(files, |f| &f.system_time),
134                SortBy::Size => Self::sort_by_key_hrtb(files, |f| &f.true_size),
135                SortBy::Exte => Self::sort_by_key_hrtb(files, |f| &f.extension),
136            }
137        } else {
138            match self.sort_by {
139                SortBy::Kind => files.sort_unstable_by(|a, b| natord::compare(&b.kind_format(), &a.kind_format())),
140                SortBy::File => files.sort_unstable_by(|a, b| natord::compare(&b.filename, &a.filename)),
141                SortBy::Date => Self::reversed_sort_by_key_hrtb(files, |f| &f.system_time),
142                SortBy::Size => Self::reversed_sort_by_key_hrtb(files, |f| &f.true_size),
143                SortBy::Exte => Self::reversed_sort_by_key_hrtb(files, |f| &f.extension),
144            }
145        }
146    }
147}
148
149impl std::fmt::Display for SortKind {
150    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
151        let sort_order = match &self.order {
152            Order::Ascending => "↓",
153            Order::Descending => "↑",
154        };
155        write!(f, "{sort_by} {sort_order}", sort_by = &self.sort_by)
156    }
157}