diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index 9139d5d..6e72a04 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -245,6 +245,11 @@ match-desktop = Match desktop dark = Dark light = Light +### Type to Search +type-to-search = Type to Search +type-to-search-recursive = Searches the current folder and all sub-folders +type-to-search-enter-path = Enters the path to the directory or file + # Context menu add-to-sidebar = Add to sidebar compress = Compress diff --git a/src/app.rs b/src/app.rs index 6c65874..00014ae 100644 --- a/src/app.rs +++ b/src/app.rs @@ -21,6 +21,7 @@ use cosmic::{ iced::{ self, clipboard::dnd::DndAction, + core::SmolStr, event, futures::{self, SinkExt}, keyboard::{Event as KeyEvent, Key, Modifiers}, @@ -64,7 +65,7 @@ use wayland_client::{protocol::wl_output::WlOutput, Proxy}; use crate::operation::{OperationError, OperationErrorType}; use crate::{ clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}, - config::{AppTheme, Config, DesktopConfig, Favorite, IconSizes, TabConfig}, + config::{AppTheme, Config, DesktopConfig, Favorite, IconSizes, TabConfig, TypeToSearch}, fl, home_dir, key_bind::key_binds, localize::LANGUAGE_SORTER, @@ -292,7 +293,7 @@ pub enum Message { ExtractHere(Option), #[cfg(all(feature = "desktop", feature = "wayland"))] Focused(window::Id), - Key(Modifiers, Key), + Key(Modifiers, Key, Option), LaunchUrl(String), MaybeExit, Modifiers(Modifiers), @@ -338,6 +339,7 @@ pub enum Message { SearchClear, SearchInput(String), SetShowDetails(bool), + SetTypeToSearch(TypeToSearch), SystemThemeModeChange(cosmic_theme::ThemeMode), Size(Size), TabActivate(Entity), @@ -1524,27 +1526,44 @@ impl App { fn settings(&self) -> Element { // TODO: Should dialog be updated here too? - widget::column::with_children(vec![widget::settings::section() - .title(fl!("appearance")) - .add({ - let app_theme_selected = match self.config.app_theme { - AppTheme::Dark => 1, - AppTheme::Light => 2, - AppTheme::System => 0, - }; - widget::settings::item::builder(fl!("theme")).control(widget::dropdown( - &self.app_themes, - Some(app_theme_selected), - move |index| { - Message::AppTheme(match index { - 1 => AppTheme::Dark, - 2 => AppTheme::Light, - _ => AppTheme::System, - }) - }, + widget::settings::view_column(vec![ + widget::settings::section() + .title(fl!("appearance")) + .add({ + let app_theme_selected = match self.config.app_theme { + AppTheme::Dark => 1, + AppTheme::Light => 2, + AppTheme::System => 0, + }; + widget::settings::item::builder(fl!("theme")).control(widget::dropdown( + &self.app_themes, + Some(app_theme_selected), + move |index| { + Message::AppTheme(match index { + 1 => AppTheme::Dark, + 2 => AppTheme::Light, + _ => AppTheme::System, + }) + }, + )) + }) + .into(), + widget::settings::section() + .title(fl!("type-to-search")) + .add(widget::radio( + widget::text::body(fl!("type-to-search-recursive")), + TypeToSearch::Recursive, + Some(self.config.type_to_search), + Message::SetTypeToSearch, )) - }) - .into()]) + .add(widget::radio( + widget::text::body(fl!("type-to-search-enter-path")), + TypeToSearch::EnterPath, + Some(self.config.type_to_search), + Message::SetTypeToSearch, + )) + .into(), + ]) .into() } } @@ -2187,13 +2206,46 @@ impl Application for App { }); } } - Message::Key(modifiers, key) => { + Message::Key(modifiers, key, text) => { let entity = self.tab_model.active(); for (key_bind, action) in self.key_binds.iter() { if key_bind.matches(modifiers, &key) { return self.update(action.message(Some(entity))); } } + + // Uncaptured keys with only shift modifiers go to the search or location box + if !modifiers.logo() + && !modifiers.control() + && !modifiers.alt() + && matches!(key, Key::Character(_)) + { + if let Some(text) = text { + match self.config.type_to_search { + TypeToSearch::Recursive => { + let mut term = self.search_get().unwrap_or_default().to_string(); + term.push_str(&text); + return self.search_set_active(Some(term)); + } + TypeToSearch::EnterPath => { + if let Some(tab) = self.tab_model.data_mut::(entity) { + let location = tab.edit_location.as_ref().map_or_else( + || tab.location.clone(), + |x| x.location.clone(), + ); + // Try to add text to end of location + if let Some(path) = location.path_opt() { + let mut path_string = path.to_string_lossy().to_string(); + path_string.push_str(&text); + tab.edit_location = Some( + location.with_path(PathBuf::from(path_string)).into(), + ); + } + } + } + } + } + } } Message::MaybeExit => { if self.window_id_opt.is_none() && self.pending_operations.is_empty() { @@ -2860,6 +2912,10 @@ impl Application for App { config_set!(show_details, show_details); return self.update_config(); } + Message::SetTypeToSearch(type_to_search) => { + config_set!(type_to_search, type_to_search); + return self.update_config(); + } Message::SystemThemeModeChange(_theme_mode) => { return self.update_config(); } @@ -4577,8 +4633,13 @@ impl Application for App { let mut subscriptions = vec![ event::listen_with(|event, status, window_id| match event { - Event::Keyboard(KeyEvent::KeyPressed { key, modifiers, .. }) => match status { - event::Status::Ignored => Some(Message::Key(modifiers, key)), + Event::Keyboard(KeyEvent::KeyPressed { + key, + modifiers, + text, + .. + }) => match status { + event::Status::Ignored => Some(Message::Key(modifiers, key, text)), event::Status::Captured => None, }, Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => { diff --git a/src/config.rs b/src/config.rs index e071a2b..7688933 100644 --- a/src/config.rs +++ b/src/config.rs @@ -95,6 +95,12 @@ impl Favorite { } } +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum TypeToSearch { + Recursive, + EnterPath, +} + #[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(default)] pub struct Config { @@ -103,6 +109,7 @@ pub struct Config { pub favorites: Vec, pub show_details: bool, pub tab: TabConfig, + pub type_to_search: TypeToSearch, } impl Config { @@ -150,6 +157,7 @@ impl Default for Config { ], show_details: false, tab: TabConfig::default(), + type_to_search: TypeToSearch::Recursive, } } } diff --git a/src/tab.rs b/src/tab.rs index f6d3f58..a0aeb40 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -1055,6 +1055,16 @@ impl std::fmt::Display for Location { } impl Location { + pub fn normalize(&self) -> Self { + if let Some(mut path) = self.path_opt().map(|x| x.to_path_buf()) { + // Add trailing slash if location is a path + path.push(""); + self.with_path(path) + } else { + self.clone() + } + } + pub fn path_opt(&self) -> Option<&PathBuf> { match self { Self::Desktop(path, ..) => Some(path), @@ -1873,7 +1883,7 @@ impl Tab { pub fn new(location: Location, config: TabConfig) -> Self { let history = vec![location.clone()]; Self { - location, + location: location.normalize(), context_menu: None, location_context_menu_point: None, location_context_menu_index: None, @@ -2198,7 +2208,7 @@ impl Tab { } pub fn change_location(&mut self, location: &Location, history_i_opt: Option) { - self.location = location.clone(); + self.location = location.normalize(); self.context_menu = None; self.edit_location = None; self.items_opt = None;