Skip to main content

kas_view/
filter.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Filters over data
7
8use kas::event::EventCx;
9use kas_widgets::edit::{EditGuard, Editor};
10use std::fmt::Debug;
11
12/// Ability to set filter
13pub trait FilterValue: Default + 'static {
14    type Value: std::fmt::Debug;
15
16    /// Update the filter
17    fn set_filter(&mut self, value: Self::Value);
18}
19
20/// Types usable as a filter
21pub trait Filter<T: ?Sized>: FilterValue {
22    /// Returns true if the given item matches this filter
23    fn matches(&self, item: &T) -> bool;
24}
25
26/// Filter: target contains self (case-sensitive string match)
27#[derive(Debug, Default, Clone, PartialEq, Eq)]
28pub struct ContainsString(String);
29
30impl ContainsString {
31    /// Construct with empty text
32    pub fn new() -> Self {
33        ContainsString(String::new())
34    }
35}
36
37impl FilterValue for ContainsString {
38    type Value = String;
39    fn set_filter(&mut self, value: String) {
40        self.0 = value;
41    }
42}
43
44impl Filter<str> for ContainsString {
45    fn matches(&self, item: &str) -> bool {
46        item.contains(&self.0)
47    }
48}
49impl Filter<String> for ContainsString {
50    fn matches(&self, item: &String) -> bool {
51        Filter::<str>::matches(self, item.as_str())
52    }
53}
54
55/// Filter: target contains self (case-insensitive string match)
56///
57// Note: the implemented method of caseless matching is not unicode compliant,
58// however works in most cases (by converting both the source and the target to
59// upper case). See [question on StackOverflow].
60//
61// [question on StackOverflow]: https://stackoverflow.com/questions/47298336/case-insensitive-string-matching-in-rust
62#[derive(Debug, Default, Clone, PartialEq, Eq)]
63pub struct ContainsCaseInsensitive(String);
64
65impl ContainsCaseInsensitive {
66    /// Construct with empty text
67    pub fn new() -> Self {
68        ContainsCaseInsensitive(String::new())
69    }
70}
71
72impl FilterValue for ContainsCaseInsensitive {
73    type Value = String;
74    fn set_filter(&mut self, value: String) {
75        self.0 = value.to_uppercase();
76    }
77}
78
79impl Filter<str> for ContainsCaseInsensitive {
80    fn matches(&self, item: &str) -> bool {
81        item.to_string().to_uppercase().contains(&self.0)
82    }
83}
84impl Filter<String> for ContainsCaseInsensitive {
85    fn matches(&self, item: &String) -> bool {
86        Filter::<str>::matches(self, item.as_str())
87    }
88}
89
90#[derive(Debug, Default)]
91pub struct SetFilter<T: Debug>(pub T);
92
93/// An [`EditGuard`] which sends a [`SetFilter`] message on every change
94///
95/// This may be used for search-as-you-type.
96pub struct KeystrokeGuard;
97impl EditGuard for KeystrokeGuard {
98    type Data = ();
99
100    fn edit(&mut self, edit: &mut Editor, cx: &mut EventCx, _: &Self::Data) {
101        cx.push(SetFilter(edit.as_str().to_string()));
102    }
103}
104
105/// An [`EditGuard`] which sends a [`SetFilter`] message on activate and focus loss
106///
107/// This may be used for search-as-you-type.
108pub struct AflGuard;
109impl EditGuard for AflGuard {
110    type Data = ();
111
112    #[inline]
113    fn focus_lost(&mut self, edit: &mut Editor, cx: &mut EventCx, _: &Self::Data) {
114        cx.push(SetFilter(edit.as_str().to_string()));
115    }
116}