From da05a85fc57973e020cbf5d4a78ee69b534729d6 Mon Sep 17 00:00:00 2001 From: sandroid Date: Tue, 17 Mar 2026 18:21:10 +0100 Subject: [PATCH] feat: start type-to-select search from the current focus --- src/tab.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index d0eac54..222a758 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -3017,7 +3017,7 @@ impl Tab { /// Returns true if an item was selected. pub fn select_by_prefix(&mut self, prefix: &str) -> bool { let prefix_lower = prefix.to_lowercase(); - self.select_focus = None; + let focus = self.select_focus.take(); if let Some(ref mut items) = self.items_opt { // First, deselect all items @@ -3025,18 +3025,63 @@ impl Tab { item.selected = false; } - // Find first matching item - for (i, item) in items.iter_mut().enumerate() { - if item.name.to_lowercase().starts_with(&prefix_lower) { - item.selected = true; - self.select_focus = Some(i); - return true; - } - } + // Determine the start index of the search. When the index is before the currently focused item, it will be + // 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 + // matches. + let start = if prefix_lower.chars().count() == 1 { + Self::index_after_focus(focus, self.sort_direction) + } else { + Self::index_before_focus(focus, self.sort_direction) + }; + let Some((until, after)) = items.split_at_mut_checked(start) else { + log::error!( + "invalid select focus index {start} for items of length {}", + items.len() + ); + 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 { + Self::select_first_prefix_match(&prefix_lower, search_items) + } else { + Self::select_first_prefix_match(&prefix_lower, search_items.rev()) + }; + + return self.select_focus.is_some(); } false } + fn index_before_focus(current_focus: Option, forward: bool) -> usize { + current_focus.map_or(0, |i| if forward { i } else { i + 1 }) + } + + fn index_after_focus(current_focus: Option, forward: bool) -> usize { + current_focus.map_or(0, |i| if forward { i + 1 } else { i }) + } + + /// Selects the first item in the given iterator whose name starts with the given prefix. + /// + /// The `prefix` must be lowercase. + fn select_first_prefix_match<'a>( + prefix: &str, + items: impl Iterator, + ) -> Option { + for (i, item) in items { + if item.name.to_lowercase().starts_with(&prefix) { + item.selected = true; + return Some(i); + } + } + None + } + pub fn select_paths(&mut self, paths: Vec) { self.select_focus = None; if let Some(ref mut items) = self.items_opt {