Skip to main content

alf/tui/state/
filter.rs

1//! Entry filtering, grouping, and sorting
2
3use super::data::EntryData;
4use crate::models::EntryType;
5
6/// Filter for entry types
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum EntryFilter {
9   /// Show all entries (globe icon)
10   All,
11   /// Show only aliases (& icon)
12   Aliases,
13   /// Show only functions (f icon)
14   Functions,
15}
16
17/// Grouping mode for entries
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum GroupMode {
20   /// All entries mixed together
21   None,
22   /// Aliases first, then functions
23   Aliases,
24   /// Functions first, then aliases
25   Functions,
26}
27
28/// Sorting order for entries
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum SortOrder {
31   /// A-Z by name
32   Ascending,
33   /// Z-A by name
34   Descending,
35}
36
37/// Filter state management
38#[derive(Debug, Clone)]
39pub struct FilterState {
40   /// Current entry type filter
41   filter: EntryFilter,
42   /// Current grouping mode
43   group_mode: GroupMode,
44   /// Current sort order
45   sort_order: SortOrder,
46}
47
48impl Default for FilterState {
49   fn default() -> Self {
50      Self {
51         filter: EntryFilter::All,
52         group_mode: GroupMode::Aliases,   // Default: aliases first
53         sort_order: SortOrder::Ascending, // Default: A-Z
54      }
55   }
56}
57
58impl FilterState {
59   /// Create a new FilterState
60   pub fn new() -> Self {
61      Self::default()
62   }
63
64   /// Get the current filter
65   pub fn filter(&self) -> EntryFilter {
66      self.filter
67   }
68
69   /// Get the current group mode
70   pub fn group_mode(&self) -> GroupMode {
71      self.group_mode
72   }
73
74   /// Get the current sort order
75   pub fn sort_order(&self) -> SortOrder {
76      self.sort_order
77   }
78
79   /// Cycle the entry type filter (forward)
80   pub fn cycle_filter(&mut self) {
81      self.filter = match self.filter {
82         EntryFilter::All => EntryFilter::Aliases,
83         EntryFilter::Aliases => EntryFilter::Functions,
84         EntryFilter::Functions => EntryFilter::All,
85      };
86   }
87
88   /// Cycle the entry type filter (backward)
89   pub fn cycle_filter_backward(&mut self) {
90      self.filter = match self.filter {
91         EntryFilter::All => EntryFilter::Functions,
92         EntryFilter::Functions => EntryFilter::Aliases,
93         EntryFilter::Aliases => EntryFilter::All,
94      };
95   }
96
97   /// Set a specific filter
98   pub fn set_filter(
99      &mut self,
100      filter: EntryFilter,
101   ) {
102      self.filter = filter;
103   }
104
105   /// Cycle to the next group mode
106   pub fn cycle_group_mode(&mut self) {
107      self.group_mode = match self.group_mode {
108         GroupMode::None => GroupMode::Aliases,
109         GroupMode::Aliases => GroupMode::Functions,
110         GroupMode::Functions => GroupMode::None,
111      };
112   }
113
114   /// Cycle to the previous group mode
115   pub fn cycle_group_mode_backward(&mut self) {
116      self.group_mode = match self.group_mode {
117         GroupMode::None => GroupMode::Functions,
118         GroupMode::Functions => GroupMode::Aliases,
119         GroupMode::Aliases => GroupMode::None,
120      };
121   }
122
123   /// Toggle sort order
124   pub fn toggle_sort_order(&mut self) {
125      self.sort_order = match self.sort_order {
126         SortOrder::Ascending => SortOrder::Descending,
127         SortOrder::Descending => SortOrder::Ascending,
128      };
129   }
130
131   /// Update visible entries based on current filter, search query, grouping, and sorting
132   pub fn update_visible_entries(
133      &self,
134      data: &mut EntryData,
135      search_query: &str,
136   ) {
137      let query = search_query.to_lowercase();
138
139      // Filter by type and search query
140      *data.visible_indices_mut() = data
141         .entries()
142         .iter()
143         .enumerate()
144         .filter(|(_, entry)| self.matches_filter(entry))
145         .filter(|(_, entry)| self.matches_search(entry, &query))
146         .map(|(idx, _)| idx)
147         .collect();
148
149      // Apply grouping and sorting
150      self.apply_grouping_and_sorting(data);
151   }
152
153   /// Check if entry matches current filter
154   fn matches_filter(
155      &self,
156      entry: &crate::models::AliasEntry,
157   ) -> bool {
158      match self.filter {
159         EntryFilter::All => true,
160         EntryFilter::Aliases => entry.entry_type == EntryType::Alias,
161         EntryFilter::Functions => entry.entry_type == EntryType::Function,
162      }
163   }
164
165   /// Check if entry matches search query
166   fn matches_search(
167      &self,
168      entry: &crate::models::AliasEntry,
169      query: &str,
170   ) -> bool {
171      if query.is_empty() {
172         return true;
173      }
174      // Simple substring matching on name, value, and comments
175      let name_match = entry.name.to_lowercase().contains(query);
176      let value_match = entry.value.to_lowercase().contains(query);
177      let comment_match = entry
178         .comments
179         .as_ref()
180         .map(|comments| comments.iter().any(|c| c.to_lowercase().contains(query)))
181         .unwrap_or(false);
182      name_match || value_match || comment_match
183   }
184
185   /// Apply grouping and sorting to visible_indices
186   fn apply_grouping_and_sorting(
187      &self,
188      data: &mut EntryData,
189   ) {
190      let sort_order = self.sort_order;
191
192      match self.group_mode {
193         GroupMode::None => {
194            // All entries mixed together, sort by name
195            data.sort_visible_indices(|entry_a, entry_b| match sort_order {
196               SortOrder::Ascending => entry_a.name.cmp(&entry_b.name),
197               SortOrder::Descending => entry_b.name.cmp(&entry_a.name),
198            });
199         },
200         GroupMode::Aliases => {
201            // Aliases first, then functions, each group sorted by name
202            data.sort_visible_indices(|entry_a, entry_b| {
203               // First, group by type (aliases before functions)
204               match (entry_a.entry_type, entry_b.entry_type) {
205                  (EntryType::Alias, EntryType::Function) => std::cmp::Ordering::Less,
206                  (EntryType::Function, EntryType::Alias) => std::cmp::Ordering::Greater,
207                  _ => {
208                     // Within same group, sort by name
209                     match sort_order {
210                        SortOrder::Ascending => entry_a.name.cmp(&entry_b.name),
211                        SortOrder::Descending => entry_b.name.cmp(&entry_a.name),
212                     }
213                  },
214               }
215            });
216         },
217         GroupMode::Functions => {
218            // Functions first, then aliases, each group sorted by name
219            data.sort_visible_indices(|entry_a, entry_b| {
220               // First, group by type (functions before aliases)
221               match (entry_a.entry_type, entry_b.entry_type) {
222                  (EntryType::Function, EntryType::Alias) => std::cmp::Ordering::Less,
223                  (EntryType::Alias, EntryType::Function) => std::cmp::Ordering::Greater,
224                  _ => {
225                     // Within same group, sort by name
226                     match sort_order {
227                        SortOrder::Ascending => entry_a.name.cmp(&entry_b.name),
228                        SortOrder::Descending => entry_b.name.cmp(&entry_a.name),
229                     }
230                  },
231               }
232            });
233         },
234      }
235   }
236}
237
238#[cfg(test)]
239#[path = "filter_tests.rs"]
240mod filter_tests;