add type-to-select option for keyboard navigation

This commit is contained in:
wowitsjack 2025-12-27 19:07:21 +10:00
parent d9b6404f1b
commit 5e92d081c6
5 changed files with 71 additions and 0 deletions

View file

@ -297,6 +297,7 @@ light = Light
type-to-search = Type to search
type-to-search-recursive = Searches the current folder and all subfolders
type-to-search-enter-path = Enters the path to the directory or file
type-to-search-select = Selects the first matching file or folder
# Context menu
add-to-sidebar = Add to sidebar

View file

@ -733,6 +733,8 @@ pub struct App {
windows: FxHashMap<window::Id, Window>,
nav_dnd_hover: Option<(Location, Instant)>,
tab_dnd_hover: Option<(Entity, Instant)>,
type_select_prefix: String,
type_select_last_key: Option<Instant>,
nav_drag_id: DragId,
tab_drag_id: DragId,
auto_scroll_speed: Option<i16>,
@ -1985,6 +1987,12 @@ impl App {
Some(self.config.type_to_search),
Message::SetTypeToSearch,
))
.add(widget::radio(
widget::text::body(fl!("type-to-search-select")),
TypeToSearch::SelectByPrefix,
Some(self.config.type_to_search),
Message::SetTypeToSearch,
))
.into(),
widget::settings::section()
.title(fl!("other"))
@ -2213,6 +2221,8 @@ impl Application for App {
windows: FxHashMap::default(),
nav_dnd_hover: None,
tab_dnd_hover: None,
type_select_prefix: String::new(),
type_select_last_key: None,
nav_drag_id: DragId::new(),
tab_drag_id: DragId::new(),
auto_scroll_speed: None,
@ -3039,6 +3049,22 @@ impl Application for App {
}
}
}
TypeToSearch::SelectByPrefix => {
// Reset buffer if timeout elapsed
if let Some(last_key) = self.type_select_last_key {
if last_key.elapsed() >= tab::TYPE_SELECT_TIMEOUT {
self.type_select_prefix.clear();
}
}
// Accumulate character and select
self.type_select_prefix.push_str(&text.to_lowercase());
self.type_select_last_key = Some(Instant::now());
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
tab.select_by_prefix(&self.type_select_prefix);
}
}
}
}
}

View file

@ -106,6 +106,7 @@ impl Favorite {
pub enum TypeToSearch {
Recursive,
EnterPath,
SelectByPrefix,
}
#[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)]

View file

@ -568,6 +568,8 @@ struct App {
FxHashSet<PathBuf>,
)>,
auto_scroll_speed: Option<i16>,
type_select_prefix: String,
type_select_last_key: Option<Instant>,
}
impl App {
@ -1038,6 +1040,8 @@ impl Application for App {
key_binds,
watcher_opt: None,
auto_scroll_speed: None,
type_select_prefix: String::new(),
type_select_last_key: None,
};
let commands = Task::batch([
@ -1448,6 +1452,20 @@ impl Application for App {
Some(location.with_path(PathBuf::from(path_string)).into());
}
}
TypeToSearch::SelectByPrefix => {
// Reset buffer if timeout elapsed
if let Some(last_key) = self.type_select_last_key {
if last_key.elapsed() >= tab::TYPE_SELECT_TIMEOUT {
self.type_select_prefix.clear();
}
}
// Accumulate character and select
self.type_select_prefix.push_str(&text.to_lowercase());
self.type_select_last_key = Some(Instant::now());
self.tab.select_by_prefix(&self.type_select_prefix);
}
}
}
}

View file

@ -98,6 +98,7 @@ use uzers::{get_group_by_gid, get_user_by_uid};
pub const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500);
pub const HOVER_DURATION: Duration = Duration::from_millis(1600);
pub const TYPE_SELECT_TIMEOUT: Duration = Duration::from_millis(1000);
//TODO: best limit for search items
const MAX_SEARCH_LATENCY: Duration = Duration::from_millis(20);
const MAX_SEARCH_RESULTS: usize = 200;
@ -2821,6 +2822,30 @@ impl Tab {
}
}
/// Selects the first item whose name starts with the given prefix (case-insensitive).
/// 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;
if let Some(ref mut items) = self.items_opt {
// First, deselect all items
for item in items.iter_mut() {
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;
}
}
}
false
}
pub fn select_paths(&mut self, paths: Vec<PathBuf>) {
self.select_focus = None;
if let Some(ref mut items) = self.items_opt {