feat: repeat a key to cycle through items in type-to-select mode

This commit is contained in:
sandroid 2026-03-17 18:29:51 +01:00 • committed by Ashley Wulber
parent da05a85fc5
commit 91243b99b5

View file

@ -3029,30 +3029,44 @@ impl Tab {
// considered first, otherwise last. Consider the focused item last when only a single character has been // considered first, otherwise last. Consider the focused item last when only a single character has been
// typed, so we eagerly switch focus on the first character and stay on the same item as long as the prefix // typed, so we eagerly switch focus on the first character and stay on the same item as long as the prefix
// matches. // matches.
let start = if prefix_lower.chars().count() == 1 { let single_char = prefix_lower.chars().count() == 1;
let start = if single_char {
Self::index_after_focus(focus, self.sort_direction) Self::index_after_focus(focus, self.sort_direction)
} else { } else {
Self::index_before_focus(focus, self.sort_direction) Self::index_before_focus(focus, self.sort_direction)
}; };
let Some((until, after)) = items.split_at_mut_checked(start) else { self.select_focus = Self::select_first_prefix_from_index(
log::error!( &prefix_lower,
"invalid select focus index {start} for items of length {}", items,
items.len() start,
); self.sort_direction,
return false; );
};
let search_items = after
.into_iter()
.enumerate()
.map(|(i, item)| (i + start, item))
.chain(until.into_iter().enumerate());
self.select_focus = if self.sort_direction { if self.select_focus.is_some() || single_char {
Self::select_first_prefix_match(&prefix_lower, search_items) return self.select_focus.is_some();
} else { }
Self::select_first_prefix_match(&prefix_lower, search_items.rev())
let mut chars = prefix_lower.chars();
let Some(first) = chars.next() else {
log::error!("search term is empty");
return self.select_focus.is_some();
}; };
// Check if all entered characters are the same
if !chars.all(|c| c == first) {
return self.select_focus.is_some();
}
// Search for a single character when all entered characters are the same.
// This allows cycling through items starting with the same character by repeatedly pressing a key.
let start = Self::index_after_focus(focus, self.sort_direction);
self.select_focus = Self::select_first_prefix_from_index(
&first.to_string(),
items,
start,
self.sort_direction,
);
return self.select_focus.is_some(); return self.select_focus.is_some();
} }
false false
@ -3066,6 +3080,33 @@ impl Tab {
current_focus.map_or(0, |i| if forward { i + 1 } else { i }) current_focus.map_or(0, |i| if forward { i + 1 } else { i })
} }
fn select_first_prefix_from_index(
prefix_lower: &str,
items: &mut [Item],
start: usize,
forward: bool,
) -> Option<usize> {
// Order the search item so they begin at `start`.
let Some((until, after)) = items.split_at_mut_checked(start) else {
log::error!(
"invalid start index {start} for items of length {}",
items.len()
);
return None;
};
let search_items = after
.into_iter()
.enumerate()
.map(|(i, item)| (i + start, item))
.chain(until.into_iter().enumerate());
if forward {
Self::select_first_prefix_match(prefix_lower, search_items)
} else {
Self::select_first_prefix_match(prefix_lower, search_items.rev())
}
}
/// Selects the first item in the given iterator whose name starts with the given prefix. /// Selects the first item in the given iterator whose name starts with the given prefix.
/// ///
/// The `prefix` must be lowercase. /// The `prefix` must be lowercase.