1use std::borrow::Cow;
2
3use crossterm::event::KeyEvent;
4use pito_footer::{Edit, Input, InputBar, Key, Tone};
5use pito_list::{Column, Keys, List, ListView, Row, Step};
6
7use crate::palette::Palette;
8
9const START: &[Key] = &[Key::Char('/')];
10const CLEAR: &[Key] = &[Key::Esc];
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum Turn {
15 Pass,
16 Taken,
17 Filtered,
18 Open(usize),
19}
20
21#[derive(Debug, Clone)]
22pub struct Filter {
23 list: List,
24 input: Input,
25 typing: bool,
26 shown: Vec<usize>,
27 label: Cow<'static, str>,
28 placeholder: Cow<'static, str>,
29 hint: Cow<'static, str>,
30 start: &'static [Key],
31 clear: &'static [Key],
32}
33
34impl Filter {
35 pub fn new(label: impl Into<Cow<'static, str>>) -> Self {
36 Filter {
37 list: List::new(),
38 input: Input::new(),
39 typing: false,
40 shown: Vec::new(),
41 label: label.into(),
42 placeholder: Cow::Borrowed(""),
43 hint: Cow::Borrowed(""),
44 start: START,
45 clear: CLEAR,
46 }
47 }
48
49 pub fn placeholder(mut self, text: impl Into<Cow<'static, str>>) -> Self {
50 self.placeholder = text.into();
51 self
52 }
53
54 pub fn hint(mut self, text: impl Into<Cow<'static, str>>) -> Self {
55 self.hint = text.into();
56 self
57 }
58
59 pub fn keys(mut self, keys: Keys) -> Self {
60 self.list = self.list.keys(keys);
61 self
62 }
63
64 pub fn start_keys(mut self, keys: &'static [Key]) -> Self {
65 self.start = keys;
66 self
67 }
68
69 pub fn clear_keys(mut self, keys: &'static [Key]) -> Self {
70 self.clear = keys;
71 self
72 }
73
74 pub fn input(mut self, input: Input) -> Self {
75 self.input = input;
76 self
77 }
78
79 pub fn typing(&self) -> bool {
80 self.typing
81 }
82
83 pub fn query(&self) -> &str {
84 self.input.value()
85 }
86
87 pub fn active(&self) -> bool {
88 !self.input.value().trim().is_empty()
89 }
90
91 pub fn shown(&self) -> &[usize] {
92 &self.shown
93 }
94
95 pub fn current(&self) -> Option<usize> {
96 self.list
97 .selected()
98 .and_then(|at| self.shown.get(at).copied())
99 }
100
101 pub fn list(&self) -> &List {
102 &self.list
103 }
104
105 pub fn list_mut(&mut self) -> &mut List {
106 &mut self.list
107 }
108
109 pub fn sift<R>(
110 &mut self,
111 rows: &[R],
112 keeps: impl Fn(&R, &str) -> bool,
113 line: impl Fn(&R) -> Row,
114 ) {
115 let kept = self.current();
116 let query = self.input.value();
117 self.shown = rows
118 .iter()
119 .enumerate()
120 .filter(|(_, row)| keeps(row, query))
121 .map(|(index, _)| index)
122 .collect();
123 let lines: Vec<Row> = self.shown.iter().map(|&index| line(&rows[index])).collect();
124 self.list.set_rows(lines);
125 let at = kept
126 .and_then(|kept| self.shown.iter().position(|&index| index == kept))
127 .unwrap_or(0);
128 self.list.select(at);
129 }
130
131 pub fn key(&mut self, key: KeyEvent) -> Turn {
132 if self.typing {
133 return match self.input.key(key.into()) {
134 Edit::Changed => Turn::Filtered,
135 Edit::Submit(_) => {
136 self.typing = false;
137 Turn::Taken
138 }
139 Edit::Cancel => {
140 self.typing = false;
141 self.input.clear();
142 Turn::Filtered
143 }
144 Edit::Pass => match self.list.key(key.into()) {
145 Step::Pass | Step::Open(_) => Turn::Pass,
146 _ => Turn::Taken,
147 },
148 _ => Turn::Taken,
149 };
150 }
151 let pressed: Key = key.into();
152 if self.start.contains(&pressed) {
153 self.typing = true;
154 return Turn::Taken;
155 }
156 if self.active() && self.clear.contains(&pressed) {
157 self.input.clear();
158 return Turn::Filtered;
159 }
160 match self.list.key(key.into()) {
161 Step::Pass => Turn::Pass,
162 Step::Open(at) => self
163 .shown
164 .get(at)
165 .map_or(Turn::Taken, |&index| Turn::Open(index)),
166 _ => Turn::Taken,
167 }
168 }
169
170 pub fn paste(&mut self, text: &str) -> Turn {
171 if !self.typing {
172 return Turn::Pass;
173 }
174 match self.input.paste(text) {
175 Edit::Changed => Turn::Filtered,
176 _ => Turn::Taken,
177 }
178 }
179
180 pub fn bar(&self) -> Option<InputBar<'_>> {
181 self.typing.then(|| {
182 let bar = InputBar::new(&self.label, &self.input)
183 .placeholder(&self.placeholder)
184 .tone(Tone::Accent);
185 if self.hint.is_empty() {
186 bar
187 } else {
188 bar.hint(&self.hint)
189 }
190 })
191 }
192
193 pub fn view<'a>(&'a mut self, columns: &'a [Column<'a>], palette: &Palette) -> ListView<'a> {
194 palette.view(&mut self.list, columns)
195 }
196}
197
198pub fn matches(text: &str, query: &str) -> bool {
199 let text = text.to_lowercase();
200 query
201 .split_whitespace()
202 .all(|word| text.contains(&word.to_lowercase()))
203}