use super::*;
pub fn menu_step(active: Option<usize>, count: usize, delta: isize) -> Option<usize> {
if count == 0 {
return None;
}
let count_i = count as isize;
let next = match active {
None => {
if delta >= 0 {
0
} else {
count_i - 1
}
}
Some(at) => (at as isize + delta).rem_euclid(count_i),
};
Some(next as usize)
}
pub fn match_rank(query: &str, label: &str) -> Option<usize> {
let query = query.trim().to_lowercase();
if query.is_empty() {
return Some(1);
}
let label = label.to_lowercase();
if label.starts_with(&query) {
Some(0)
} else if label.contains(&query) {
Some(1)
} else {
None
}
}
pub fn filter_indices<S: AsRef<str>>(query: &str, labels: &[S]) -> Vec<usize> {
let mut ranked: Vec<(usize, usize)> = labels
.iter()
.enumerate()
.filter_map(|(ix, label)| match_rank(query, label.as_ref()).map(|rank| (rank, ix)))
.collect();
ranked.sort_by_key(|&(rank, ix)| (rank, ix));
ranked.into_iter().map(|(_, ix)| ix).collect()
}
pub struct Filter {
items: Vec<SharedString>,
filtered: Vec<usize>,
active: Option<usize>,
}
impl Filter {
pub fn new(items: Vec<SharedString>) -> Self {
let filtered: Vec<usize> = (0..items.len()).collect();
let active = (!filtered.is_empty()).then_some(0);
Self {
items,
filtered,
active,
}
}
pub fn items(&self) -> &[SharedString] {
&self.items
}
pub fn filtered(&self) -> &[usize] {
&self.filtered
}
pub fn active(&self) -> Option<usize> {
self.active
}
pub fn refilter(&mut self, query: &str) {
self.filtered = filter_indices(query, &self.items);
self.active = (!self.filtered.is_empty()).then_some(0);
}
pub fn step(&mut self, delta: isize) {
self.active = menu_step(self.active, self.filtered.len(), delta);
}
pub fn set_active(&mut self, position: usize) {
if position < self.filtered.len() {
self.active = Some(position);
}
}
pub fn active_item(&self) -> Option<usize> {
self.active
.and_then(|position| self.filtered.get(position))
.copied()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MenuKey {
Up,
Down,
Enter,
ModEnter,
Escape,
Backspace,
Other,
}
pub fn classify_key(key: &str, cmd: bool, ctrl: bool) -> MenuKey {
match key {
"up" => MenuKey::Up,
"down" => MenuKey::Down,
"n" if ctrl => MenuKey::Down,
"p" if ctrl => MenuKey::Up,
"enter" if cmd || ctrl => MenuKey::ModEnter,
"enter" => MenuKey::Enter,
"escape" => MenuKey::Escape,
"backspace" => MenuKey::Backspace,
_ => MenuKey::Other,
}
}