1use super::*;
4
5pub fn menu_step(active: Option<usize>, count: usize, delta: isize) -> Option<usize> {
8 if count == 0 {
9 return None;
10 }
11 let count_i = count as isize;
12 let next = match active {
13 None => {
14 if delta >= 0 {
15 0
16 } else {
17 count_i - 1
18 }
19 }
20 Some(at) => (at as isize + delta).rem_euclid(count_i),
21 };
22 Some(next as usize)
23}
24
25pub fn match_rank(query: &str, label: &str) -> Option<usize> {
29 let query = query.trim().to_lowercase();
30 if query.is_empty() {
31 return Some(1);
32 }
33 let label = label.to_lowercase();
34 if label.starts_with(&query) {
35 Some(0)
36 } else if label.contains(&query) {
37 Some(1)
38 } else {
39 None
40 }
41}
42
43pub fn filter_indices<S: AsRef<str>>(query: &str, labels: &[S]) -> Vec<usize> {
46 let mut ranked: Vec<(usize, usize)> = labels
47 .iter()
48 .enumerate()
49 .filter_map(|(ix, label)| match_rank(query, label.as_ref()).map(|rank| (rank, ix)))
50 .collect();
51 ranked.sort_by_key(|&(rank, ix)| (rank, ix));
52 ranked.into_iter().map(|(_, ix)| ix).collect()
53}
54
55pub struct Filter {
59 items: Vec<SharedString>,
60 filtered: Vec<usize>,
62 active: Option<usize>,
64}
65
66impl Filter {
67 pub fn new(items: Vec<SharedString>) -> Self {
68 let filtered: Vec<usize> = (0..items.len()).collect();
69 let active = (!filtered.is_empty()).then_some(0);
70 Self {
71 items,
72 filtered,
73 active,
74 }
75 }
76
77 pub fn items(&self) -> &[SharedString] {
78 &self.items
79 }
80
81 pub fn filtered(&self) -> &[usize] {
83 &self.filtered
84 }
85
86 pub fn active(&self) -> Option<usize> {
89 self.active
90 }
91
92 pub fn refilter(&mut self, query: &str) {
95 self.filtered = filter_indices(query, &self.items);
96 self.active = (!self.filtered.is_empty()).then_some(0);
97 }
98
99 pub fn step(&mut self, delta: isize) {
100 self.active = menu_step(self.active, self.filtered.len(), delta);
101 }
102
103 pub fn set_active(&mut self, position: usize) {
107 if position < self.filtered.len() {
108 self.active = Some(position);
109 }
110 }
111
112 pub fn active_item(&self) -> Option<usize> {
117 self.active
118 .and_then(|position| self.filtered.get(position))
119 .copied()
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum MenuKey {
126 Up,
127 Down,
128 Enter,
130 ModEnter,
132 Escape,
133 Backspace,
134 Other,
135}
136
137pub fn classify_key(key: &str, cmd: bool, ctrl: bool) -> MenuKey {
138 match key {
139 "up" => MenuKey::Up,
140 "down" => MenuKey::Down,
141 "n" if ctrl => MenuKey::Down,
145 "p" if ctrl => MenuKey::Up,
146 "enter" if cmd || ctrl => MenuKey::ModEnter,
147 "enter" => MenuKey::Enter,
148 "escape" => MenuKey::Escape,
149 "backspace" => MenuKey::Backspace,
150 _ => MenuKey::Other,
151 }
152}