irongit

feat: filter modes (fuzzy, contains, and exact)

huncholanehuncholaneauthored
parent f7f7996commit f1c8dfe7ebbd1e9fb2d0e12fc07fbc94de4b93f0Browse files

1 file changed, +65 -2

+65-2src/widgets/miller.rs
@@ -110,10 +110,52 @@ pub struct MillerFilterResponse {
110110
111111 /// Always-visible filter bar for a miller column.
112112 /// Shows a text input with placeholder. Focus with shortcut key, Escape unfocuses.
113+#[derive(Debug, Clone, Copy, PartialEq)]
114+pub enum FilterMode {
115+ Fuzzy, // chars in order anywhere
116+ Contains, // case-insensitive substring
117+ Exact, // case-insensitive full match
118+}
119+
120+impl FilterMode {
121+ fn next(self) -> Self {
122+ match self {
123+ FilterMode::Fuzzy => FilterMode::Contains,
124+ FilterMode::Contains => FilterMode::Exact,
125+ FilterMode::Exact => FilterMode::Fuzzy,
126+ }
127+ }
128+
129+ fn icon(self) -> &'static str {
130+ match self {
131+ FilterMode::Fuzzy => egui_phosphor::regular::WAVES,
132+ FilterMode::Contains => egui_phosphor::regular::TEXTBOX,
133+ FilterMode::Exact => egui_phosphor::regular::EQUALS,
134+ }
135+ }
136+
137+ fn color(self) -> egui::Color32 {
138+ match self {
139+ FilterMode::Fuzzy => crate::theme::CatppuccinMocha::PEACH,
140+ FilterMode::Contains => crate::theme::CatppuccinMocha::BLUE,
141+ FilterMode::Exact => crate::theme::CatppuccinMocha::GREEN,
142+ }
143+ }
144+
145+ fn tooltip(self) -> &'static str {
146+ match self {
147+ FilterMode::Fuzzy => "Fuzzy (ctrl-f to cycle)",
148+ FilterMode::Contains => "Contains (ctrl-f to cycle)",
149+ FilterMode::Exact => "Exact (ctrl-f to cycle)",
150+ }
151+ }
152+}
153+
113154 pub struct MillerFilter {
114155 pub query: String,
115156 id: &'static str,
116157 focus_next: bool,
158+ pub mode: FilterMode,
117159 }
118160
119161 impl MillerFilter {
@@ -122,6 +164,7 @@ impl MillerFilter {
122164 query: String::new(),
123165 id,
124166 focus_next: false,
167+ mode: FilterMode::Fuzzy,
125168 }
126169 }
127170
@@ -156,6 +199,17 @@ impl MillerFilter {
156199 .size(12.0),
157200 );
158201
202+ // Filter mode toggle
203+ if ui.add(
204+ egui::Button::new(
205+ egui::RichText::new(self.mode.icon())
206+ .color(self.mode.color())
207+ .size(11.0)
208+ ).frame(false)
209+ ).on_hover_text(self.mode.tooltip()).clicked() {
210+ self.mode = self.mode.next();
211+ }
212+
159213 let id = egui::Id::new(self.id);
160214 let r = ui.add(
161215 egui::TextEdit::singleline(&mut self.query)
@@ -180,6 +234,10 @@ impl MillerFilter {
180234
181235 if has_focus || lost_focus {
182236 if has_focus {
237+ // Ctrl-F: cycle filter mode
238+ if ui.input_mut(|i| i.consume_key(egui::Modifiers::CTRL, egui::Key::F)) {
239+ self.mode = self.mode.next();
240+ }
183241 // Escape: unfocus (don't clear)
184242 if ui.input(|i| i.key_pressed(egui::Key::Escape)) {
185243 r.surrender_focus();
@@ -202,7 +260,7 @@ impl MillerFilter {
202260 resp
203261 }
204262
205- /// Fuzzy match a label against the query. Returns true if it matches (or query is empty).
263+ /// Match a label against the query using the current filter mode.
206264 /// Supports `|` to OR multiple terms (e.g. "start|report" matches either).
207265 pub fn matches(&self, label: &str) -> bool {
208266 if self.query.is_empty() {
@@ -211,7 +269,12 @@ impl MillerFilter {
211269 self.query.split('|')
212270 .any(|term| {
213271 let term = term.trim();
214- !term.is_empty() && fuzzy_matches(term, label)
272+ if term.is_empty() { return false; }
273+ match self.mode {
274+ FilterMode::Fuzzy => fuzzy_matches(term, label),
275+ FilterMode::Contains => label.to_lowercase().contains(&term.to_lowercase()),
276+ FilterMode::Exact => label.to_lowercase() == term.to_lowercase(),
277+ }
215278 })
216279 }
217280