From 5f729829d754da3605a014c7a9ab934837650180 Mon Sep 17 00:00:00 2001 From: Cheong Lau <234708519+Cheong-Lau@users.noreply.github.com> Date: Sun, 26 Oct 2025 16:20:51 +1000 Subject: [PATCH 1/2] chore: more pedantic clippy suggestions --- src/app.rs | 781 ++++++++++++++++++------------------ src/archive.rs | 8 +- src/clipboard.rs | 18 +- src/config.rs | 18 +- src/dialog.rs | 293 +++++++------- src/lib.rs | 45 ++- src/localize.rs | 2 +- src/menu.rs | 22 +- src/mime_app.rs | 52 +-- src/mounter/gvfs.rs | 37 +- src/mouse_area.rs | 59 ++- src/operation/controller.rs | 2 +- src/operation/mod.rs | 38 +- src/operation/recursive.rs | 36 +- src/tab.rs | 574 ++++++++++++++------------ src/thumbnail_cacher.rs | 47 ++- src/thumbnailer.rs | 29 +- 17 files changed, 1063 insertions(+), 998 deletions(-) diff --git a/src/app.rs b/src/app.rs index 5b1abe1..07f8530 100644 --- a/src/app.rs +++ b/src/app.rs @@ -199,78 +199,76 @@ pub enum Action { } impl Action { - fn message(&self, entity_opt: Option) -> Message { + const fn message(&self, entity_opt: Option) -> Message { match self { - Action::About => Message::ToggleContextPage(ContextPage::About), - Action::AddToSidebar => Message::AddToSidebar(entity_opt), - Action::Compress => Message::Compress(entity_opt), - Action::Copy => Message::Copy(entity_opt), - Action::Cut => Message::Cut(entity_opt), - Action::CosmicSettingsDesktop => Message::CosmicSettings("desktop"), - Action::CosmicSettingsDisplays => Message::CosmicSettings("displays"), - Action::CosmicSettingsWallpaper => Message::CosmicSettings("wallpaper"), - Action::Delete => Message::Delete(entity_opt), - Action::DesktopViewOptions => Message::DesktopViewOptions, - Action::EditHistory => Message::ToggleContextPage(ContextPage::EditHistory), - Action::EditLocation => { - Message::TabMessage(entity_opt, tab::Message::EditLocationEnable) - } - Action::Eject => Message::Eject, - Action::EmptyTrash => Message::TabMessage(None, tab::Message::EmptyTrash), - Action::ExtractHere => Message::ExtractHere(entity_opt), - Action::ExtractTo => Message::ExtractTo(entity_opt), + Self::About => Message::ToggleContextPage(ContextPage::About), + Self::AddToSidebar => Message::AddToSidebar(entity_opt), + Self::Compress => Message::Compress(entity_opt), + Self::Copy => Message::Copy(entity_opt), + Self::Cut => Message::Cut(entity_opt), + Self::CosmicSettingsDesktop => Message::CosmicSettings("desktop"), + Self::CosmicSettingsDisplays => Message::CosmicSettings("displays"), + Self::CosmicSettingsWallpaper => Message::CosmicSettings("wallpaper"), + Self::Delete => Message::Delete(entity_opt), + Self::DesktopViewOptions => Message::DesktopViewOptions, + Self::EditHistory => Message::ToggleContextPage(ContextPage::EditHistory), + Self::EditLocation => Message::TabMessage(entity_opt, tab::Message::EditLocationEnable), + Self::Eject => Message::Eject, + Self::EmptyTrash => Message::TabMessage(None, tab::Message::EmptyTrash), + Self::ExtractHere => Message::ExtractHere(entity_opt), + Self::ExtractTo => Message::ExtractTo(entity_opt), #[cfg(feature = "desktop")] - Action::ExecEntryAction(action) => { + Self::ExecEntryAction(action) => { Message::TabMessage(entity_opt, tab::Message::ExecEntryAction(None, *action)) } - Action::Gallery => Message::TabMessage(entity_opt, tab::Message::GalleryToggle), - Action::HistoryNext => Message::TabMessage(entity_opt, tab::Message::GoNext), - Action::HistoryPrevious => Message::TabMessage(entity_opt, tab::Message::GoPrevious), - Action::ItemDown => Message::TabMessage(entity_opt, tab::Message::ItemDown), - Action::ItemLeft => Message::TabMessage(entity_opt, tab::Message::ItemLeft), - Action::ItemRight => Message::TabMessage(entity_opt, tab::Message::ItemRight), - Action::ItemUp => Message::TabMessage(entity_opt, tab::Message::ItemUp), - Action::LocationUp => Message::TabMessage(entity_opt, tab::Message::LocationUp), - Action::NewFile => Message::NewItem(entity_opt, false), - Action::NewFolder => Message::NewItem(entity_opt, true), - Action::Open => Message::TabMessage(entity_opt, tab::Message::Open(None)), - Action::OpenInNewTab => Message::OpenInNewTab(entity_opt), - Action::OpenInNewWindow => Message::OpenInNewWindow(entity_opt), - Action::OpenItemLocation => Message::OpenItemLocation(entity_opt), - Action::OpenTerminal => Message::OpenTerminal(entity_opt), - Action::OpenWith => Message::OpenWithDialog(entity_opt), - Action::Paste => Message::Paste(entity_opt), - Action::PermanentlyDelete => Message::PermanentlyDelete(entity_opt), - Action::Preview => Message::Preview(entity_opt), - Action::Reload => Message::TabMessage(entity_opt, tab::Message::Reload), - Action::RemoveFromRecents => Message::RemoveFromRecents(entity_opt), - Action::Rename => Message::Rename(entity_opt), - Action::RestoreFromTrash => Message::RestoreFromTrash(entity_opt), - Action::SearchActivate => Message::SearchActivate, - Action::SelectAll => Message::TabMessage(entity_opt, tab::Message::SelectAll), - Action::SelectFirst => Message::TabMessage(entity_opt, tab::Message::SelectFirst), - Action::SelectLast => Message::TabMessage(entity_opt, tab::Message::SelectLast), - Action::SetSort(sort, dir) => { + Self::Gallery => Message::TabMessage(entity_opt, tab::Message::GalleryToggle), + Self::HistoryNext => Message::TabMessage(entity_opt, tab::Message::GoNext), + Self::HistoryPrevious => Message::TabMessage(entity_opt, tab::Message::GoPrevious), + Self::ItemDown => Message::TabMessage(entity_opt, tab::Message::ItemDown), + Self::ItemLeft => Message::TabMessage(entity_opt, tab::Message::ItemLeft), + Self::ItemRight => Message::TabMessage(entity_opt, tab::Message::ItemRight), + Self::ItemUp => Message::TabMessage(entity_opt, tab::Message::ItemUp), + Self::LocationUp => Message::TabMessage(entity_opt, tab::Message::LocationUp), + Self::NewFile => Message::NewItem(entity_opt, false), + Self::NewFolder => Message::NewItem(entity_opt, true), + Self::Open => Message::TabMessage(entity_opt, tab::Message::Open(None)), + Self::OpenInNewTab => Message::OpenInNewTab(entity_opt), + Self::OpenInNewWindow => Message::OpenInNewWindow(entity_opt), + Self::OpenItemLocation => Message::OpenItemLocation(entity_opt), + Self::OpenTerminal => Message::OpenTerminal(entity_opt), + Self::OpenWith => Message::OpenWithDialog(entity_opt), + Self::Paste => Message::Paste(entity_opt), + Self::PermanentlyDelete => Message::PermanentlyDelete(entity_opt), + Self::Preview => Message::Preview(entity_opt), + Self::Reload => Message::TabMessage(entity_opt, tab::Message::Reload), + Self::RemoveFromRecents => Message::RemoveFromRecents(entity_opt), + Self::Rename => Message::Rename(entity_opt), + Self::RestoreFromTrash => Message::RestoreFromTrash(entity_opt), + Self::SearchActivate => Message::SearchActivate, + Self::SelectAll => Message::TabMessage(entity_opt, tab::Message::SelectAll), + Self::SelectFirst => Message::TabMessage(entity_opt, tab::Message::SelectFirst), + Self::SelectLast => Message::TabMessage(entity_opt, tab::Message::SelectLast), + Self::SetSort(sort, dir) => { Message::TabMessage(entity_opt, tab::Message::SetSort(*sort, *dir)) } - Action::Settings => Message::ToggleContextPage(ContextPage::Settings), - Action::TabClose => Message::TabClose(entity_opt), - Action::TabNew => Message::TabNew, - Action::TabNext => Message::TabNext, - Action::TabPrev => Message::TabPrev, - Action::TabViewGrid => Message::TabView(entity_opt, tab::View::Grid), - Action::TabViewList => Message::TabView(entity_opt, tab::View::List), - Action::ToggleFoldersFirst => Message::ToggleFoldersFirst, - Action::ToggleShowHidden => Message::ToggleShowHidden, - Action::ToggleSort(sort) => { + Self::Settings => Message::ToggleContextPage(ContextPage::Settings), + Self::TabClose => Message::TabClose(entity_opt), + Self::TabNew => Message::TabNew, + Self::TabNext => Message::TabNext, + Self::TabPrev => Message::TabPrev, + Self::TabViewGrid => Message::TabView(entity_opt, tab::View::Grid), + Self::TabViewList => Message::TabView(entity_opt, tab::View::List), + Self::ToggleFoldersFirst => Message::ToggleFoldersFirst, + Self::ToggleShowHidden => Message::ToggleShowHidden, + Self::ToggleSort(sort) => { Message::TabMessage(entity_opt, tab::Message::ToggleSort(*sort)) } - Action::WindowClose => Message::WindowClose, - Action::WindowNew => Message::WindowNew, - Action::ZoomDefault => Message::ZoomDefault(entity_opt), - Action::ZoomIn => Message::ZoomIn(entity_opt), - Action::ZoomOut => Message::ZoomOut(entity_opt), - Action::Recents => Message::Recents, + Self::WindowClose => Message::WindowClose, + Self::WindowNew => Message::WindowNew, + Self::ZoomDefault => Message::ZoomDefault(entity_opt), + Self::ZoomIn => Message::ZoomIn(entity_opt), + Self::ZoomOut => Message::ZoomOut(entity_opt), + Self::Recents => Message::Recents, } } } @@ -469,14 +467,14 @@ pub enum ArchiveType { } impl ArchiveType { - pub fn all() -> &'static [Self] { + pub const fn all() -> &'static [Self] { &[Self::Tgz, Self::Zip] } - pub fn extension(&self) -> &str { + pub const fn extension(&self) -> &str { match self { - ArchiveType::Tgz => ".tgz", - ArchiveType::Zip => ".zip", + Self::Tgz => ".tgz", + Self::Zip => ".zip", } } } @@ -565,7 +563,7 @@ impl Default for DialogPages { } impl DialogPages { - pub fn new() -> Self { + pub const fn new() -> Self { Self { pages: VecDeque::new(), } @@ -762,7 +760,7 @@ impl App { // First launch apps that can be launched directly if mime == "application/x-desktop" { // Try opening desktop application - App::launch_desktop_entries(&paths); + Self::launch_desktop_entries(&paths); continue; } else if mime == "application/x-executable" || mime == "application/vnd.appimage" { // Try opening executable @@ -774,14 +772,12 @@ impl App { io::ErrorKind::PermissionDenied => { // If permission is denied, try marking as executable, then running tasks.push(self.push_dialog( - DialogPage::SetExecutableAndLaunch { - path: path.to_path_buf(), - }, + DialogPage::SetExecutableAndLaunch { path: path.clone() }, Some(SET_EXECUTABLE_AND_LAUNCH_CONFIRM_BUTTON_ID.clone()), )); } _ => { - log::warn!("failed to execute {:?}: {}", path, err); + log::warn!("failed to execute {}: {}", path.display(), err); } }, } @@ -809,13 +805,13 @@ impl App { Ok(()) => { let _ = recently_used_xbel::update_recently_used( &path, - App::APP_ID.to_string(), + Self::APP_ID.to_string(), "cosmic-files".to_string(), None, ); } Err(err) => { - log::warn!("failed to open {:?}: {}", path, err); + log::warn!("failed to open {}: {}", path.display(), err); } } } @@ -832,20 +828,26 @@ impl App { Some(commands) => { for mut command in commands { if let Err(err) = spawn_detached(&mut command) { - log::warn!("failed to execute {:?}: {}", path, err); + log::warn!("failed to execute {}: {}", path.display(), err); } } } None => { - log::warn!("failed to parse {:?}: invalid Desktop Entry/Exec", path); + log::warn!( + "failed to parse {}: invalid Desktop Entry/Exec", + path.display() + ); } }, None => { - log::warn!("failed to parse {:?}: missing Desktop Entry/Exec", path); + log::warn!( + "failed to parse {}: missing Desktop Entry/Exec", + path.display() + ); } }, Err(err) => { - log::warn!("failed to parse {:?}: {}", path, err); + log::warn!("failed to parse {}: {}", path.display(), err); } } } @@ -867,7 +869,7 @@ impl App { for path in paths { let _ = recently_used_xbel::update_recently_used( &path.into(), - App::APP_ID.to_string(), + Self::APP_ID.to_string(), "cosmic-files".to_string(), None, ); @@ -896,7 +898,7 @@ impl App { } #[cfg(feature = "desktop")] - fn exec_entry_action(entry: cosmic::desktop::DesktopEntryData, action: usize) { + fn exec_entry_action(entry: &cosmic::desktop::DesktopEntryData, action: usize) { if let Some(action) = entry.desktop_actions.get(action) { // Largely copied from COSMIC app library let mut exec = shlex::Shlex::new(&action.exec); @@ -924,7 +926,7 @@ impl App { if let Some(destination) = paths .first() .and_then(|first| first.as_ref().parent()) - .map(|parent| parent.to_path_buf()) + .map(Path::to_path_buf) { let (mut dialog, dialog_task) = Dialog::new( DialogSettings::new() @@ -1112,7 +1114,7 @@ impl App { let can_trash = match path.metadata() { Ok(metadata) => matches!(tab::fs_kind(&metadata), tab::FsKind::Local), Err(err) => { - log::warn!("failed to get metadata for {:?}: {}", path, err); + log::warn!("failed to get metadata for {}: {}", path.display(), err); false } }; @@ -1198,7 +1200,7 @@ impl App { } fn rescan_operation_selection(&mut self, op_sel: OperationSelection) -> Task { - log::info!("rescan_operation_selection {:?}", op_sel); + log::info!("rescan_operation_selection {op_sel:?}"); let entity = self.tab_model.active(); let Some(tab) = self.tab_model.data::(entity) else { return Task::none(); @@ -1206,7 +1208,7 @@ impl App { let Some(items) = tab.items_opt() else { return Task::none(); }; - for item in items.iter() { + for item in items { if item.selected { if let Some(path) = item.path_opt() { if op_sel.selected.contains(path) || op_sel.ignored.contains(path) { @@ -1255,7 +1257,7 @@ impl App { let mounter_paths: Vec<_> = mounter_items .iter() .flat_map(|item| item.1.iter()) - .filter_map(|item| item.path()) + .filter_map(MounterItem::path) .collect(); if !mounter_paths.is_empty() { for item in &mut items { @@ -1274,7 +1276,7 @@ impl App { )) } Err(err) => { - log::warn!("failed to rescan: {}", err); + log::warn!("failed to rescan: {err}"); cosmic::action::none() } } @@ -1287,7 +1289,7 @@ impl App { let mut needs_reload = Vec::new(); for entity in self.tab_model.iter() { if let Some(tab) = self.tab_model.data::(entity) { - if let Location::Trash = &tab.location { + if matches!(&tab.location, Location::Trash) { needs_reload.push((entity, Location::Trash)); } } @@ -1321,7 +1323,7 @@ impl App { let mut needs_reload = Vec::new(); for entity in self.tab_model.iter() { if let Some(tab) = self.tab_model.data::(entity) { - if let Location::Recents = &tab.location { + if matches!(&tab.location, Location::Recents) { needs_reload.push((entity, Location::Recents)); } } @@ -1360,7 +1362,7 @@ impl App { Some(term) => tab.location.path_opt().map(|path| { ( Location::Search( - path.to_path_buf(), + path.clone(), term, tab.config.show_hidden, Instant::now(), @@ -1369,7 +1371,7 @@ impl App { ) }), None => match &tab.location { - Location::Search(path, ..) => Some((Location::Path(path.to_path_buf()), false)), + Location::Search(path, ..) => Some((Location::Path(path.clone()), false)), _ => None, }, }; @@ -1400,7 +1402,7 @@ impl App { if let Some(tab) = self.tab_model.data::(entity) { for location in tab.selected_locations() { if let Some(path) = location.path_opt() { - paths.push(path.to_path_buf()); + paths.push(path.clone()); } } } @@ -1440,7 +1442,7 @@ impl App { entity, Location::Desktop(path.clone(), output.clone(), self.config.desktop), )); - }; + } } } let mut commands = Vec::with_capacity(needs_reload.len()); @@ -1457,8 +1459,7 @@ impl App { let nav_bar_id = self.nav_model.iter().find(|&id| { self.nav_model .data::(id) - .map(|l| l == location) - .unwrap_or_default() + .is_some_and(|l| l == location) }); if let Some(id) = nav_bar_id { @@ -1534,8 +1535,8 @@ impl App { // Collect all mounter items let mut nav_items = Vec::new(); - for (key, items) in self.mounter_items.iter() { - for item in items.iter() { + for (key, items) in &self.mounter_items { + for item in items { nav_items.push((*key, item)); } } @@ -1545,7 +1546,7 @@ impl App { for (i, (key, item)) in nav_items.into_iter().enumerate() { nav_model = nav_model.insert(|mut b| { b = b.text(item.name()).data(MounterData(key, item.clone())); - let uri = item.uri().to_string(); + let uri = item.uri(); if let Some(path) = item.path() { b = b.data(Location::Network(uri, item.name(), Some(path))); } else if !uri.is_empty() { @@ -1615,34 +1616,34 @@ impl App { for entity in self.tab_model.iter() { if let Some(tab) = self.tab_model.data::(entity) { if let Some(path) = tab.location.path_opt() { - new_paths.insert(path.to_path_buf()); + new_paths.insert(path.clone()); } } } // Unwatch paths no longer used - for path in old_paths.iter() { + for path in &old_paths { if !new_paths.contains(path) { match watcher.unwatch(path) { Ok(()) => { - log::debug!("unwatching {:?}", path); + log::debug!("unwatching {}", path.display()); } Err(err) => { - log::debug!("failed to unwatch {:?}: {}", path, err); + log::debug!("failed to unwatch {}: {}", path.display(), err); } } } } // Watch new paths - for path in new_paths.iter() { + for path in &new_paths { if !old_paths.contains(path) { match watcher.watch(path, notify::RecursiveMode::NonRecursive) { Ok(()) => { - log::debug!("watching {:?}", path); + log::debug!("watching {}", path.display()); } Err(err) => { - log::debug!("failed to watch {:?}: {}", path, err); + log::debug!("failed to watch {}: {}", path.display(), err); } } } @@ -1734,7 +1735,7 @@ impl App { let icon_size: u16 = config.icon_size.into(); section = section.add( widget::settings::item::builder(fl!("icon-size")) - .description(format!("{}%", icon_size)) + .description(format!("{icon_size}%")) .control( widget::slider(50..=500, icon_size, move |icon_size| { Message::DesktopConfig(DesktopConfig { @@ -1749,7 +1750,7 @@ impl App { let grid_spacing: u16 = config.grid_spacing.into(); section = section.add( widget::settings::item::builder(fl!("grid-spacing")) - .description(format!("{}%", grid_spacing)) + .description(format!("{grid_spacing}%")) .control( widget::slider(50..=500, grid_spacing, move |grid_spacing| { Message::DesktopConfig(DesktopConfig { @@ -1872,7 +1873,7 @@ impl App { PreviewKind::Location(location) => { if let Some(tab) = self.tab_model.data::(entity) { if let Some(items) = tab.items_opt() { - for item in items.iter() { + for item in items { if item.location_opt.as_ref() == Some(location) { children.push( item.preview_view(Some(&self.mime_app_cache), military_time), @@ -1888,7 +1889,7 @@ impl App { PreviewKind::Selected => { if let Some(tab) = self.tab_model.data::(entity) { if let Some(items) = tab.items_opt() { - for item in items.iter() { + for item in items { if item.selected { children.push( item.preview_view(Some(&self.mime_app_cache), military_time), @@ -2048,12 +2049,9 @@ impl App { } } Err(err) => { - log::warn!( - "failed to update favorites after moving directories: {:?}", - err, - ); + log::warn!("failed to update favorites after moving directories: {err:?}",); } - }; + } } else { self.config.favorites = favorites; log::warn!( @@ -2126,7 +2124,7 @@ impl Application for App { while let Some(task) = compio_rx.recv().await { compio::runtime::spawn(task).detach(); } - }) + }); }); let about = About::default() @@ -2145,7 +2143,7 @@ impl Application for App { ), ]); - let mut app = App { + let mut app = Self { core, about, nav_bar_context_id: segmented_button::Entity::null(), @@ -2207,7 +2205,7 @@ impl Application for App { commands.push(app.open_tab( Location::Path(parent.to_path_buf()), true, - Some(vec![path.to_path_buf()]), + Some(vec![path.clone()]), )); continue; } @@ -2369,11 +2367,11 @@ impl Application for App { None } else { // TODO do we need to choose the correct mounter? - self.mounter_items.keys().map(|k| *k).next() + self.mounter_items.keys().copied().next() }) { if let Some(mounter) = MOUNTERS.get(&key) { - return mounter.network_drive(uri.clone()).map(move |_| { + return mounter.network_drive(uri.clone()).map(move |()| { cosmic::Action::App(Message::NetworkDriveOpenEntityAfterMount { entity, }) @@ -2381,7 +2379,10 @@ impl Application for App { } } - log::warn!("failed to open favorite, path does not exist: {:?}", path); + log::warn!( + "failed to open favorite, path does not exist: {}", + path.display() + ); return self.push_dialog( DialogPage::FavoritePathError { path: path.clone(), @@ -2394,7 +2395,10 @@ impl Application for App { match path.try_exists() { Ok(true) => true, Ok(false) => { - log::warn!("failed to open favorite, path does not exist: {:?}", path); + log::warn!( + "failed to open favorite, path does not exist: {}", + path.display() + ); return self.push_dialog( DialogPage::FavoritePathError { path: path.clone(), @@ -2404,7 +2408,11 @@ impl Application for App { ); } Err(err) => { - log::warn!("failed to open favorite for path: {:?}, {}", path, err); + log::warn!( + "failed to open favorite for path: {}, {}", + path.display(), + err + ); return self.push_dialog( DialogPage::FavoritePathError { path: path.clone(), @@ -2428,7 +2436,7 @@ impl Application for App { if let Some(mounter) = MOUNTERS.get(&data.0) { return mounter .mount(data.1.clone()) - .map(|_| cosmic::action::none()); + .map(|()| cosmic::action::none()); } } Task::none() @@ -2566,7 +2574,6 @@ impl Application for App { .location_opt .clone() .unwrap() - .clone() { Some((uri, name, path.clone())) } else { @@ -2646,40 +2653,34 @@ impl Application for App { match spawn_detached(&mut command) { Ok(()) => {} Err(err) => { - log::warn!("failed to run cosmic-settings {}: {}", arg, err) + log::warn!("failed to run cosmic-settings {arg}: {err}"); } } } Message::Delete(entity_opt) => { let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); if let Some(tab) = self.tab_model.data::(entity) { - match &tab.location { - Location::Trash => { - if let Some(items) = tab.items_opt() { - let mut trash_items = Vec::new(); - for item in items.iter() { - if item.selected { - match &item.metadata { - ItemMetadata::Trash { entry, .. } => { - trash_items.push(entry.clone()); - } - _ => { - //TODO: error on trying to permanently delete non-trash file? - } - } + if tab.location == Location::Trash { + if let Some(items) = tab.items_opt() { + let mut trash_items = Vec::new(); + for item in items { + if item.selected { + if let ItemMetadata::Trash { entry, .. } = &item.metadata { + trash_items.push(entry.clone()); + } else { + //TODO: error on trying to permanently delete non-trash file? } } - if !trash_items.is_empty() { - return self - .operation(Operation::DeleteTrash { items: trash_items }); - } + } + if !trash_items.is_empty() { + return self + .operation(Operation::DeleteTrash { items: trash_items }); } } - _ => { - let paths = self.selected_paths(entity_opt); - if !paths.is_empty() { - return self.delete(paths); - } + } else { + let paths = self.selected_paths(entity_opt); + if !paths.is_empty() { + return self.delete(paths); } } } @@ -2738,15 +2739,15 @@ impl Application for App { self.windows .insert(id, WindowKind::Dialogs(widget::Id::unique())); return command.map(|_id| cosmic::Action::None); - } else { - let mut tasks = Vec::new(); - for (id, kind) in self.windows.iter() { - if matches!(kind, WindowKind::Dialogs(_)) { - tasks.push(window::close(*id)); - } - } - return Task::batch(tasks); } + + let mut tasks = Vec::new(); + for (id, kind) in &self.windows { + if matches!(kind, WindowKind::Dialogs(_)) { + tasks.push(window::close(*id)); + } + } + return Task::batch(tasks); } } Message::DialogCancel => { @@ -2766,7 +2767,7 @@ impl Application for App { password, } => { let extension = archive_type.extension(); - let name = format!("{}{}", name, extension); + let name = format!("{name}{extension}"); let to = to.join(name); tasks.push(self.operation(Operation::Compress { paths, @@ -2779,7 +2780,7 @@ impl Application for App { tasks.push(self.operation(Operation::EmptyTrash)); } DialogPage::FailedOperation(id) => { - log::warn!("TODO: retry operation {}", id); + log::warn!("TODO: retry operation {id}"); } DialogPage::ExtractPassword { id, password } => { let (operation, _, _err) = self.failed_operations.get(&id).unwrap(); @@ -2799,7 +2800,7 @@ impl Application for App { error: _, } => { if let Some(mounter) = MOUNTERS.get(&mounter_key) { - tasks.push(mounter.mount(item).map(|_| cosmic::action::none())); + tasks.push(mounter.mount(item).map(|()| cosmic::action::none())); } } DialogPage::NetworkAuth { @@ -2849,24 +2850,24 @@ impl Application for App { Ok(()) => { let _ = recently_used_xbel::update_recently_used( &path, - App::APP_ID.to_string(), + Self::APP_ID.to_string(), "cosmic-files".to_string(), None, ); } Err(err) => { log::warn!( - "failed to open {:?} with {:?}: {}", - path, + "failed to open {} with {:?}: {}", + path.display(), app.id, err - ) + ); } } } else { log::warn!( - "failed to open {:?} with {:?}: failed to get command", - path, + "failed to open {} with {:?}: failed to get command", + path.display(), app.id ); } @@ -2918,7 +2919,7 @@ impl Application for App { if let Some(destination) = paths .first() .and_then(|first| first.parent()) - .map(|parent| parent.to_path_buf()) + .map(Path::to_path_buf) { return self.operation(Operation::Extract { paths, @@ -2967,7 +2968,7 @@ impl Application for App { let in_surface_ids = false; if self.core.main_window_id() == Some(window_id) || in_surface_ids { let entity = self.tab_model.active(); - for (key_bind, action) in self.key_binds.iter() { + for (key_bind, action) in &self.key_binds { if key_bind.matches(modifiers, &key) { return self.update(action.message(Some(entity))); } @@ -3020,7 +3021,7 @@ impl Application for App { Message::LaunchUrl(url) => match open::that_detached(&url) { Ok(()) => {} Err(err) => { - log::warn!("failed to open {:?}: {}", url, err); + log::warn!("failed to open {url:?}: {err}"); } }, Message::ModifiersChanged(window_id, modifiers) => { @@ -3037,11 +3038,11 @@ impl Application for App { // Check for unmounted folders let mut unmounted = Vec::new(); if let Some(old_items) = self.mounter_items.get(&mounter_key) { - for old_item in old_items.iter() { + for old_item in old_items { if let Some(old_path) = old_item.path() { if old_item.is_mounted() { let mut still_mounted = false; - for item in mounter_items.iter() { + for item in &mounter_items { if let Some(path) = item.path() { if path == old_path && item.is_mounted() { still_mounted = true; @@ -3068,8 +3069,7 @@ impl Application for App { if unmounted.iter().any(|unmounted| { tab.location .path_opt() - .map(|location| location.starts_with(unmounted)) - .unwrap_or(false) + .is_some_and(|location| location.starts_with(unmounted)) }) { tab.change_location(&home_location, None); Some(tab.title()) @@ -3104,13 +3104,13 @@ impl Application for App { } Message::MountResult(mounter_key, item, res) => match res { Ok(true) => { - log::info!("connected to {:?}", item); + log::info!("connected to {item:?}"); } Ok(false) => { - log::info!("cancelled connection to {:?}", item); + log::info!("cancelled connection to {item:?}"); } Err(error) => { - log::warn!("failed to connect to {:?}: {}", item, error); + log::warn!("failed to connect to {item:?}: {error}"); return self.push_dialog( DialogPage::MountError { mounter_key, @@ -3142,7 +3142,7 @@ impl Application for App { Some((*mounter_key, self.network_drive_input.clone())); return mounter .network_drive(self.network_drive_input.clone()) - .map(|_| cosmic::action::none()); + .map(|()| cosmic::action::none()); } log::warn!( "no mounter found for connecting to {:?}", @@ -3155,16 +3155,16 @@ impl Application for App { } match res { Ok(true) => { - log::info!("connected to {:?}", uri); + log::info!("connected to {uri:?}"); if matches!(self.context_page, ContextPage::NetworkDrive) { self.set_show_context(false); } } Ok(false) => { - log::info!("cancelled connection to {:?}", uri); + log::info!("cancelled connection to {uri:?}"); } Err(error) => { - log::warn!("failed to connect to {:?}: {}", uri, error); + log::warn!("failed to connect to {uri:?}: {error}"); return self.dialog_pages.push_back(DialogPage::NetworkError { mounter_key, uri, @@ -3179,7 +3179,7 @@ impl Application for App { if let Some(path) = &tab.location.path_opt() { return Task::batch([ self.dialog_pages.push_back(DialogPage::NewItem { - parent: path.to_path_buf(), + parent: (*path).clone(), name: String::new(), dir, }), @@ -3193,7 +3193,7 @@ impl Application for App { self.notification_opt = Some(notification); } Message::NotifyEvents(events) => { - log::debug!("{:?}", events); + log::debug!("{events:?}"); let mut needs_reload = Vec::new(); let entities: Vec<_> = self.tab_model.iter().collect(); @@ -3201,51 +3201,47 @@ impl Application for App { if let Some(tab) = self.tab_model.data_mut::(entity) { if let Some(path) = &tab.location.path_opt() { let mut contains_change = false; - for event in events.iter() { - for event_path in event.paths.iter() { + for event in &events { + for event_path in &event.paths { if event_path.starts_with(path) { - match event.kind { - notify::EventKind::Modify( - notify::event::ModifyKind::Metadata(_), - ) - | notify::EventKind::Modify( - notify::event::ModifyKind::Data(_), - ) => { - // If metadata or data changed, find the matching item and reload it - //TODO: this could be further optimized by looking at what exactly changed - if let Some(items) = &mut tab.items_opt { - for item in items.iter_mut() { - if item.path_opt() == Some(event_path) { - //TODO: reload more, like mime types? - match fs::metadata(event_path) { - Ok(new_metadata) => { - if let ItemMetadata::Path { - metadata, - .. - } = &mut item.metadata - { - *metadata = new_metadata - } - } - - Err(err) => { - log::warn!( - "failed to reload metadata for {:?}: {}", - path, - err - ); + if let notify::EventKind::Modify( + notify::event::ModifyKind::Metadata(_) + | notify::event::ModifyKind::Data(_), + ) = event.kind + { + // If metadata or data changed, find the matching item and reload it + //TODO: this could be further optimized by looking at what exactly changed + if let Some(items) = &mut tab.items_opt { + for item in items.iter_mut() { + if item.path_opt() == Some(event_path) { + //TODO: reload more, like mime types? + match fs::metadata(event_path) { + Ok(new_metadata) => { + if let ItemMetadata::Path { + metadata, + .. + } = &mut item.metadata + { + *metadata = new_metadata; } } - //TODO item.thumbnail_opt = + + Err(err) => { + log::warn!( + "failed to reload metadata for {}: {}", + path.display(), + err + ); + } } + //TODO item.thumbnail_opt = } } } - _ => { - // Any other events reload the whole tab - contains_change = true; - break; - } + } else { + // Any other events reload the whole tab + contains_change = true; + break; } } } @@ -3280,16 +3276,16 @@ impl Application for App { if let Some(tab) = self.tab_model.data_mut::(entity) { if let Some(path) = &tab.location.path_opt() { if let Some(items) = tab.items_opt() { - for item in items.iter() { + for item in items { if item.selected { if let Some(path) = item.path_opt() { - paths.push(path.to_path_buf()); + paths.push(path.clone()); } } } } if paths.is_empty() { - paths.push(path.to_path_buf()); + paths.push((*path).clone()); } } } @@ -3301,11 +3297,11 @@ impl Application for App { command.current_dir(&path); if let Err(err) = spawn_detached(&mut command) { log::warn!( - "failed to open {:?} with terminal {:?}: {}", - path, + "failed to open {} with terminal {:?}: {}", + path.display(), terminal.id, err - ) + ); } } else { log::warn!("failed to get command for {:?}", terminal.id); @@ -3332,11 +3328,11 @@ impl Application for App { .for_each(|path| match process::Command::new(&exe).arg(path).spawn() { Ok(_child) => {} Err(err) => { - log::error!("failed to execute {:?}: {}", exe, err); + log::error!("failed to execute {}: {}", exe.display(), err); } }), Err(err) => { - log::error!("failed to get current executable path: {}", err); + log::error!("failed to get current executable path: {err}"); } }, Message::OpenItemLocation(entity_opt) => { @@ -3363,7 +3359,7 @@ impl Application for App { app.command(&[&url]).and_then(|v| v.into_iter().next()) { if let Err(err) = spawn_detached(&mut command) { - log::warn!("failed to open {:?} with {:?}: {}", url, app.id, err) + log::warn!("failed to open {:?} with {:?}: {}", url, app.id, err); } } else { log::warn!( @@ -3393,7 +3389,7 @@ impl Application for App { }; return self.push_dialog( DialogPage::OpenWith { - path: path.to_path_buf(), + path: path.clone(), mime: item.mime.clone(), selected: 0, store_opt: "x-scheme-handler/mime" @@ -3454,7 +3450,7 @@ impl Application for App { } } Message::PendingCancelAll => { - for (id, (_, controller)) in self.pending_operations.iter() { + for (id, (_, controller)) in &self.pending_operations { controller.cancel(); self.progress_operations.remove(id); } @@ -3540,7 +3536,7 @@ impl Application for App { OperationErrorType::Generic(_) => DialogPage::FailedOperation(id), OperationErrorType::PasswordRequired => DialogPage::ExtractPassword { id, - password: String::from(""), + password: String::new(), }, })); } @@ -3573,7 +3569,7 @@ impl Application for App { } } Message::PendingPauseAll(pause) => { - for (_id, (_, controller)) in self.pending_operations.iter() { + for (_, controller) in self.pending_operations.values() { if pause { controller.pause(); } else { @@ -3644,8 +3640,7 @@ impl Application for App { let maybe_entity = self.nav_model.iter().find(|&entity| { self.nav_model .data::(entity) - .map(|loc| matches!(loc, Location::Trash)) - .unwrap_or_default() + .is_some_and(|loc| matches!(loc, Location::Trash)) }); if let Some(entity) = maybe_entity { self.nav_model @@ -3659,10 +3654,10 @@ impl Application for App { if let Some(tab) = self.tab_model.data_mut::(entity) { if let Some(items) = tab.items_opt() { let mut selected = Vec::new(); - for item in items.iter() { + for item in items { if item.selected { if let Some(path) = item.path_opt() { - selected.push(path.to_path_buf()); + selected.push(path.clone()); } } } @@ -3716,15 +3711,12 @@ impl Application for App { let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); if let Some(tab) = self.tab_model.data_mut::(entity) { if let Some(items) = tab.items_opt() { - for item in items.iter() { + for item in items { if item.selected { - match &item.metadata { - ItemMetadata::Trash { entry, .. } => { - trash_items.push(entry.clone()); - } - _ => { - //TODO: error on trying to restore non-trash file? - } + if let ItemMetadata::Trash { entry, .. } = &item.metadata { + trash_items.push(entry.clone()); + } else { + //TODO: error on trying to restore non-trash file? } } } @@ -3738,7 +3730,7 @@ impl Application for App { let entity = self.tab_model.active(); return self.update(Message::TabMessage( Some(entity), - tab::Message::ScrollTab((scroll_speed as f32) / 10.0), + tab::Message::ScrollTab(f32::from(scroll_speed) / 10.0), )); } Message::SearchActivate => { @@ -3837,17 +3829,15 @@ impl Application for App { tasks.push(Task::future(async move { cosmic::action::app(Message::WindowClose) })); - } else { - if let Some(position) = self.tab_model.position(entity) { - let new_position = if position > 0 { - position - 1 - } else { - position + 1 - }; + } else if let Some(position) = self.tab_model.position(entity) { + let new_position = if position > 0 { + position - 1 + } else { + position + 1 + }; - if let Some(new_entity) = self.tab_model.entity_at(new_position) { - tasks.push(self.update(Message::TabActivate(new_entity))); - } + if let Some(new_entity) = self.tab_model.entity_at(new_position) { + tasks.push(self.update(Message::TabActivate(new_entity))); } } @@ -3929,72 +3919,69 @@ impl Application for App { } tab::Command::ContextMenu(point_opt, parent_id) => { #[cfg(feature = "wayland")] - match point_opt { - Some(point) => { - if crate::is_wayland() { - // Open context menu - use cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{ - Anchor, Gravity, - }; - use cosmic::iced_runtime::platform_specific::wayland::popup::{ - SctkPopupSettings, SctkPositioner, - }; - let window_id = WindowId::unique(); - self.windows.insert( - window_id, - WindowKind::ContextMenu(entity, widget::Id::unique()), - ); - commands.push(self.update(Message::Surface( - cosmic::surface::action::app_popup( - move |app: &mut App| -> SctkPopupSettings { - let anchor_rect = Rectangle { - x: point.x as i32, - y: point.y as i32, - width: 1, - height: 1, - }; - let positioner = SctkPositioner { - size: None, - anchor_rect, - anchor: Anchor::None, - gravity: Gravity::BottomRight, - reactive: true, - ..Default::default() - }; - SctkPopupSettings { - parent: parent_id.unwrap_or( - app.core - .main_window_id() - .unwrap_or(WindowId::NONE), - ), - id: window_id, - positioner, - parent_size: None, - grab: true, - close_with_children: false, - input_zone: None, - } - }, - None, - ), - ))); - } + if let Some(point) = point_opt { + if crate::is_wayland() { + // Open context menu + use cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{ + Anchor, Gravity, + }; + use cosmic::iced_runtime::platform_specific::wayland::popup::{ + SctkPopupSettings, SctkPositioner, + }; + let window_id = WindowId::unique(); + self.windows.insert( + window_id, + WindowKind::ContextMenu(entity, widget::Id::unique()), + ); + commands.push(self.update(Message::Surface( + cosmic::surface::action::app_popup( + move |app: &mut Self| -> SctkPopupSettings { + let anchor_rect = Rectangle { + x: point.x as i32, + y: point.y as i32, + width: 1, + height: 1, + }; + let positioner = SctkPositioner { + size: None, + anchor_rect, + anchor: Anchor::None, + gravity: Gravity::BottomRight, + reactive: true, + ..Default::default() + }; + SctkPopupSettings { + parent: parent_id.unwrap_or( + app.core + .main_window_id() + .unwrap_or(WindowId::NONE), + ), + id: window_id, + positioner, + parent_size: None, + grab: true, + close_with_children: false, + input_zone: None, + } + }, + None, + ), + ))); } - None => { - // Destroy previous popup - let mut window_ids = Vec::new(); - for (window_id, window_kind) in self.windows.iter() { - if let WindowKind::ContextMenu(e, _) = window_kind { - if *e == entity { - window_ids.push(*window_id); - } + } else { + // Destroy previous popup + let mut window_ids = Vec::new(); + for (window_id, window_kind) in &self.windows { + if let WindowKind::ContextMenu(e, _) = window_kind { + if *e == entity { + window_ids.push(*window_id); } } - for window_id in window_ids { - commands.push(self.update(Message::Surface( - cosmic::surface::action::destroy_popup(window_id), - ))); - } + } + for window_id in window_ids { + commands.push(self.update(Message::Surface( + cosmic::surface::action::destroy_popup(window_id), + ))); } } } @@ -4010,7 +3997,7 @@ impl Application for App { } #[cfg(feature = "desktop")] tab::Command::ExecEntryAction(entry, action) => { - App::exec_entry_action(entry, action); + Self::exec_entry_action(&entry, action); } tab::Command::Iced(iced_command) => { commands.push(iced_command.0.map(move |x| { @@ -4025,11 +4012,11 @@ impl Application for App { Ok(exe) => match process::Command::new(&exe).arg(path).spawn() { Ok(_child) => {} Err(err) => { - log::error!("failed to execute {:?}: {}", exe, err); + log::error!("failed to execute {}: {}", exe.display(), err); } }, Err(err) => { - log::error!("failed to get current executable path: {}", err); + log::error!("failed to get current executable path: {err}"); } }, tab::Command::OpenTrash => { @@ -4039,7 +4026,7 @@ impl Application for App { match spawn_detached(&mut command) { Ok(()) => {} Err(err) => { - log::warn!("failed to run cosmic-files --trash: {}", err) + log::warn!("failed to run cosmic-files --trash: {err}"); } } } @@ -4067,7 +4054,7 @@ impl Application for App { tab::Command::SetSort(location, heading_options, direction) => { let default_sort = tab::SORT_OPTION_FALLBACK .get(&location) - .cloned() + .copied() .unwrap_or((HeadingOptions::Name, true)); let changed = if default_sort == (heading_options, direction) { self.state.sort_names.remove(&location).is_some() @@ -4210,7 +4197,7 @@ impl Application for App { } } Err(err) => { - log::warn!("failed to rescan: {}", err); + log::warn!("failed to rescan: {err}"); } } @@ -4242,11 +4229,11 @@ impl Application for App { Ok(exe) => match process::Command::new(&exe).spawn() { Ok(_child) => {} Err(err) => { - log::error!("failed to execute {:?}: {}", exe, err); + log::error!("failed to execute {}: {}", exe.display(), err); } }, Err(err) => { - log::error!("failed to get current executable path: {}", err); + log::error!("failed to get current executable path: {err}"); } }, Message::ZoomDefault(entity_opt) => { @@ -4312,7 +4299,7 @@ impl Application for App { if let Some(location) = self.nav_model.data::(entity) { self.nav_dnd_hover = Some((location.clone(), Instant::now())); let location = location.clone(); - return Task::perform(tokio::time::sleep(HOVER_DURATION), move |_| { + return Task::perform(tokio::time::sleep(HOVER_DURATION), move |()| { cosmic::Action::App(Message::DndHoverLocTimeout(location.clone())) }); } @@ -4373,7 +4360,7 @@ impl Application for App { } Message::DndEnterTab(entity) => { self.tab_dnd_hover = Some((entity, Instant::now())); - return Task::perform(tokio::time::sleep(HOVER_DURATION), move |_| { + return Task::perform(tokio::time::sleep(HOVER_DURATION), move |()| { cosmic::Action::App(Message::DndHoverTabTimeout(entity)) }); } @@ -4391,19 +4378,20 @@ impl Application for App { Location::Trash if matches!(action, DndAction::Move) => { self.delete(data.paths) } - _ => match tab.location.path_opt() { - Some(path) => self.update(Message::PasteContents( - path.clone(), - ClipboardPaste { - kind, - paths: data.paths, - }, - )), - None => { + _ => { + if let Some(path) = tab.location.path_opt() { + self.update(Message::PasteContents( + path.clone(), + ClipboardPaste { + kind, + paths: data.paths, + }, + )) + } else { log::warn!("{:?} to {:?} is not supported.", action, tab.location); Task::none() } - }, + } }; return ret; } @@ -4423,7 +4411,7 @@ impl Application for App { if let Some(mounter) = MOUNTERS.get(&data.0) { return mounter .unmount(data.1.clone()) - .map(|_| cosmic::action::none()); + .map(|()| cosmic::action::none()); } } } @@ -4458,14 +4446,14 @@ impl Application for App { .nav_model .data::(entity) .and_then(|x| x.path_opt()) - .map(|x| x.to_path_buf()) + .cloned() { match tab::item_from_path(&path, IconSizes::default()) { Ok(item) => { return self.push_dialog( DialogPage::OpenWith { - path: path.to_path_buf(), - mime: item.mime.clone(), + path, + mime: item.mime, selected: 0, store_opt: "x-scheme-handler/mime" .parse::() @@ -4478,7 +4466,11 @@ impl Application for App { ); } Err(err) => { - log::warn!("failed to get item for path {:?}: {}", path, err); + log::warn!( + "failed to get item for path {}: {}", + path.display(), + err + ); } } } @@ -4529,21 +4521,20 @@ impl Application for App { } _ => { log::error!( - "unsupported location for open in new window: {:?}", - location + "unsupported location for open in new window: {location:?}" ); break 'open_in_new_window; } - }; + } match command.spawn() { Ok(_child) => {} Err(err) => { - log::error!("failed to execute {:?}: {}", exe, err); + log::error!("failed to execute {}: {}", exe.display(), err); } - }; + } } Err(err) => { - log::error!("failed to get current executable path: {}", err); + log::error!("failed to get current executable path: {err}"); } } } @@ -4564,7 +4555,11 @@ impl Application for App { self.set_show_context(true); } Err(err) => { - log::warn!("failed to get item from path {:?}: {}", path, err); + log::warn!( + "failed to get item from path {}: {}", + path.display(), + err + ); } } } @@ -4721,7 +4716,7 @@ impl Application for App { { return mounter .unmount(item.clone()) - .map(|_| cosmic::action::none()); + .map(|()| cosmic::action::none()); } } } @@ -4752,7 +4747,7 @@ impl Application for App { self.state.sort_names.clone(), ) { - log::warn!("Failed to save sort names: {:?}", err); + log::warn!("Failed to save sort names: {err:?}"); } } } @@ -4810,7 +4805,7 @@ impl Application for App { let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); if let Some(tab) = self.tab_model.data::(entity) { if let Some(items) = tab.items_opt() { - for item in items.iter() { + for item in items { if item.selected { actions.extend(item.preview_header().into_iter().map(|element| { element.map(move |x| Message::TabMessage(Some(entity), x)) @@ -4819,7 +4814,7 @@ impl Application for App { } } } - }; + } context_drawer::context_drawer( self.preview(entity_opt, kind, true) .map(move |x| Message::TabMessage(Some(entity), x)), @@ -4875,7 +4870,7 @@ impl Application for App { None } else { let extension = archive_type.extension(); - let name = format!("{}{}", name, extension); + let name = format!("{name}{extension}"); let path = to.join(&name); if path.exists() { dialog = @@ -4909,7 +4904,7 @@ impl Application for App { Message::DialogUpdate(DialogPage::Compress { paths: paths.clone(), to: to.clone(), - name: name.clone(), + name, archive_type: *archive_type, password: password.clone(), }) @@ -4955,9 +4950,7 @@ impl Application for App { password: Some(password_unwrapped), }) }) - .on_submit_maybe( - complete_maybe.clone().map(|maybe| move |_| maybe.clone()), - ) + .on_submit_maybe(complete_maybe.map(|maybe| move |_| maybe.clone())) .into(), ])); } @@ -4982,7 +4975,7 @@ impl Application for App { //TODO: nice description of error widget::dialog() .title("Failed operation") - .body(format!("{:#?}\n{}", operation, err)) + .body(format!("{operation:#?}\n{err}")) .icon(icon::from_name("dialog-error").size(64)) //TODO: retry action .primary_action( @@ -5228,9 +5221,7 @@ impl Application for App { dir: *dir, }) }) - .on_submit_maybe( - complete_maybe.clone().map(|maybe| move |_| maybe.clone()), - ) + .on_submit_maybe(complete_maybe.map(|maybe| move |_| maybe.clone())) .into(), ]) .spacing(space_xxs), @@ -5317,7 +5308,7 @@ impl Application for App { .map_or(480.0, |size| (size.height - 256.0).min(480.0)); // (32 (item_height) + 5.0 (custom button padding)) + (space_xxs (list item spacing) * 2) let scrollable_height = available_apps.len() as f32 - * (item_height + 5.0 + (2.0 * space_xxs as f32)); + * f32::from(space_xxs).mul_add(2.0, item_height + 5.0); if scrollable_height > max_size { Length::Fixed(max_size) @@ -5339,10 +5330,10 @@ impl Application for App { let target = if paths.len() == 1 { format!( "\"{}\"", - paths[0] - .file_name() - .map(std::ffi::OsStr::to_string_lossy) - .unwrap_or_else(|| paths[0].to_string_lossy()) + paths[0].file_name().map_or_else( + || paths[0].to_string_lossy(), + std::ffi::OsStr::to_string_lossy + ) ) } else { fl!("selected-items", items = paths.len()) @@ -5432,9 +5423,7 @@ impl Application for App { dir: *dir, }) }) - .on_submit_maybe( - complete_maybe.clone().map(|maybe| move |_| maybe.clone()), - ) + .on_submit_maybe(complete_maybe.map(|maybe| move |_| maybe.clone())) .into(), ]) .spacing(space_xxs), @@ -5557,7 +5546,7 @@ impl Application for App { let mut total_progress = 0.0; let mut count = 0; let mut all_paused = true; - for (_id, (op, controller)) in self.pending_operations.iter() { + for (op, controller) in self.pending_operations.values() { if !controller.is_paused() { all_paused = false; } @@ -5572,7 +5561,7 @@ impl Application for App { } let running = count; // Adjust the progress bar so it does not jump around when operations finish - for id in self.progress_operations.iter() { + for id in &self.progress_operations { if self.complete_operations.contains_key(id) { total_progress += 1.0; count += 1; @@ -5721,7 +5710,7 @@ impl Application for App { .on_input(Message::SearchInput), ) .padding(space_xxs), - ) + ); } } @@ -5747,16 +5736,13 @@ impl Application for App { } let entity = self.tab_model.active(); - match self.tab_model.data::(entity) { - Some(tab) => { - let tab_view = tab - .view(&self.key_binds) - .map(move |message| Message::TabMessage(Some(entity), message)); - tab_column = tab_column.push(tab_view); - } - None => { - //TODO - } + if let Some(tab) = self.tab_model.data::(entity) { + let tab_view = tab + .view(&self.key_binds) + .map(move |message| Message::TabMessage(Some(entity), message)); + tab_column = tab_column.push(tab_view); + } else { + //TODO } // The toaster is added on top of an empty element to ensure that it does not override context menus @@ -5805,7 +5791,7 @@ impl Application for App { vertical_space().height(margin.0).into(), tab_column.into(), vertical_space().height(margin.2).into(), - ]) + ]); } if margin.1 >= 0. || margin.3 >= 0. { Element::from(widget::row::with_children(vec![ @@ -5941,7 +5927,7 @@ impl Application for App { move |events_res: notify_debouncer_full::DebounceEventResult| { match events_res { Ok(mut events) => { - log::debug!("{:?}", events); + log::debug!("{events:?}"); events.retain(|event| { match &event.kind { @@ -5969,15 +5955,14 @@ impl Application for App { Ok(()) => {} Err(err) => { log::warn!( - "failed to send notify events: {:?}", - err + "failed to send notify events: {err:?}" ); } } } } Err(err) => { - log::warn!("failed to watch files: {:?}", err); + log::warn!("failed to watch files: {err:?}"); } } }, @@ -5994,12 +5979,12 @@ impl Application for App { { Ok(()) => {} Err(err) => { - log::warn!("failed to send notify watcher: {:?}", err); + log::warn!("failed to send notify watcher: {err:?}"); } } } Err(err) => { - log::warn!("failed to create file watcher: {:?}", err); + log::warn!("failed to create file watcher: {err:?}"); } } @@ -6032,7 +6017,7 @@ impl Application for App { } } Err(e) => { - log::warn!("failed to watch trash bin for changes: {e:?}") + log::warn!("failed to watch trash bin for changes: {e:?}"); } }, ); @@ -6067,10 +6052,10 @@ impl Application for App { std::future::pending().await } (Err(e), _) => { - log::warn!("failed to create new watcher for trash bin: {e:?}") + log::warn!("failed to create new watcher for trash bin: {e:?}"); } (_, Err(e)) => { - log::warn!("could not find any valid trash bins to watch: {e:?}") + log::warn!("could not find any valid trash bins to watch: {e:?}"); } } @@ -6187,7 +6172,7 @@ impl Application for App { subscriptions.push( cosmic::iced::time::every(Duration::from_millis(100)) .map(|_| Message::None), - ) + ); } } else { // Handle notification when window is closed and operations are in progress @@ -6216,7 +6201,7 @@ impl Application for App { }); } Err(err) => { - log::warn!("failed to create notification: {}", err); + log::warn!("failed to create notification: {err}"); } } }) @@ -6503,7 +6488,7 @@ pub(crate) mod test_utils { // Check lengths. // `items_opt` is optional and the directory at `path` may have zero entries // Therefore, this doesn't panic if `items_opt` is None - let items_len = tab.items_opt().map(|items| items.len()).unwrap_or_default(); + let items_len = tab.items_opt().map(Vec::len).unwrap_or_default(); assert_eq!(entries.len(), items_len); let empty = Vec::new(); diff --git a/src/archive.rs b/src/archive.rs index 9bd3b32..75af4cf 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -55,7 +55,7 @@ pub fn extract( .map(flate2::read::GzDecoder::new) .map(tar::Archive::new) .and_then(|mut archive| archive.unpack(new_dir)) - .map_err(|e| OperationError::from_err(e, controller))? + .map_err(|e| OperationError::from_err(e, controller))?; } "application/x-tar" => OpReader::new(path, controller.clone()) .map(io::BufReader::new) @@ -93,10 +93,10 @@ pub fn extract( .map(|reader| lzma_rust2::XzReader::new(reader, true)) .map(tar::Archive::new) .and_then(|mut archive| archive.unpack(new_dir)) - .map_err(|e| OperationError::from_err(e, controller))? + .map_err(|e| OperationError::from_err(e, controller))?; } _ => Err(OperationError::from_err( - format!("unsupported mime type {:?}", mime), + format!("unsupported mime type {mime:?}"), controller, ))?, } @@ -262,7 +262,7 @@ fn zip_extract>( // Ensure we update children's permissions before making a parent unwritable files_by_unix_mode.sort_by_key(|(path, _)| Reverse(path.clone())); } - for (path, mode) in files_by_unix_mode.into_iter() { + for (path, mode) in files_by_unix_mode { fs::set_permissions(&path, fs::Permissions::from_mode(mode))?; } } diff --git a/src/clipboard.rs b/src/clipboard.rs index 62c5bca..b0144b9 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -42,7 +42,7 @@ impl ClipboardCopy { .to_string(); //TODO: do we have to use \r\n? let cr_nl = "\r\n"; - for path in paths.iter() { + for path in paths { let path = path.as_ref(); match path.to_str() { @@ -56,8 +56,8 @@ impl ClipboardCopy { None => { //TODO: allow non-UTF-8? log::warn!( - "{:?} is not valid UTF-8, not adding to text/plain clipboard", - path + "{} is not valid UTF-8, not adding to text/plain clipboard", + path.display() ); } } @@ -74,8 +74,8 @@ impl ClipboardCopy { } Err(()) => { log::warn!( - "{:?} cannot be turned into a URL, not adding to text/uri-list clipboard", - path + "{} cannot be turned into a URL, not adding to text/uri-list clipboard", + path.display() ); } } @@ -135,7 +135,7 @@ impl TryFrom<(Vec, String)> for ClipboardPaste { let url = Url::parse(line)?; match url.to_file_path() { Ok(path) => paths.push(path), - Err(()) => Err(format!("invalid file URL {:?}", url))?, + Err(()) => Err(format!("invalid file URL {url:?}"))?, } } } @@ -146,18 +146,18 @@ impl TryFrom<(Vec, String)> for ClipboardPaste { kind = match line { "copy" => ClipboardKind::Copy, "cut" => ClipboardKind::Cut { is_dnd: false }, - _ => Err(format!("unsupported clipboard operation {:?}", line))?, + _ => Err(format!("unsupported clipboard operation {line:?}"))?, }; } else { let url = Url::parse(line)?; match url.to_file_path() { Ok(path) => paths.push(path), - Err(()) => Err(format!("invalid file URL {:?}", url))?, + Err(()) => Err(format!("invalid file URL {url:?}"))?, } } } } - _ => Err(format!("unsupported mime type {:?}", mime))?, + _ => Err(format!("unsupported mime type {mime:?}"))?, } Ok(Self { kind, paths }) } diff --git a/src/config.rs b/src/config.rs index ada8dcb..8689121 100644 --- a/src/config.rs +++ b/src/config.rs @@ -135,18 +135,18 @@ impl State { pub fn load() -> (Option, Self) { match cosmic_config::Config::new_state(App::APP_ID, CONFIG_VERSION) { Ok(config_handler) => { - let config = match State::get_entry(&config_handler) { + let config = match Self::get_entry(&config_handler) { Ok(ok) => ok, Err((errs, config)) => { - log::info!("errors loading config: {:?}", errs); + log::info!("errors loading config: {errs:?}"); config } }; (Some(config_handler), config) } Err(err) => { - log::error!("failed to create config handler: {}", err); - (None, State::default()) + log::error!("failed to create config handler: {err}"); + (None, Self::default()) } } } @@ -178,18 +178,18 @@ impl Config { pub fn load() -> (Option, Self) { match cosmic_config::Config::new(App::APP_ID, CONFIG_VERSION) { Ok(config_handler) => { - let config = match Config::get_entry(&config_handler) { + let config = match Self::get_entry(&config_handler) { Ok(ok) => ok, Err((errs, config)) => { - log::info!("errors loading config: {:?}", errs); + log::info!("errors loading config: {errs:?}"); config } }; (Some(config_handler), config) } Err(err) => { - log::error!("failed to create config handler: {}", err); - (None, Config::default()) + log::error!("failed to create config handler: {err}"); + (None, Self::default()) } } } @@ -204,7 +204,7 @@ impl Config { } /// Construct tab config for dialog - pub fn dialog_tab(&self) -> TabConfig { + pub const fn dialog_tab(&self) -> TabConfig { TabConfig { folders_first: self.dialog.folders_first, icon_sizes: self.dialog.icon_sizes, diff --git a/src/dialog.rs b/src/dialog.rs index 4b042b5..0a4f063 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -86,15 +86,15 @@ impl DialogKind { } } - pub fn is_dir(&self) -> bool { + pub const fn is_dir(&self) -> bool { matches!(self, Self::OpenFolder | Self::OpenMultipleFolders) } - pub fn multiple(&self) -> bool { + pub const fn multiple(&self) -> bool { matches!(self, Self::OpenMultipleFiles | Self::OpenMultipleFolders) } - pub fn save(&self) -> bool { + pub const fn save(&self) -> bool { matches!(self, Self::SaveFile { .. }) } } @@ -200,7 +200,7 @@ impl> From for DialogLabel { impl<'a, M: Clone + 'static> From<&'a DialogLabel> for Element<'a, M> { fn from(label: &'a DialogLabel) -> Self { let mut iced_spans = Vec::with_capacity(label.spans.len()); - for span in label.spans.iter() { + for span in &label.spans { iced_spans.push(cosmic::iced::widget::span(&span.text).underline(span.underline)); } cosmic::iced::widget::rich_text(iced_spans).into() @@ -276,7 +276,7 @@ impl Dialog { settings.platform_specific.application_id = dialog_settings.app_id; } - let (window_id, window_command) = window::open(settings.clone()); + let (window_id, window_command) = window::open(settings); let mut core = Core::default(); core.set_main_window_id(Some(window_id)); @@ -286,7 +286,7 @@ impl Dialog { match fs::canonicalize(path) { Ok(ok) => Some(ok), Err(err) => { - log::warn!("failed to canonicalize {:?}: {}", path, err); + log::warn!("failed to canonicalize {}: {}", path.display(), err); None } } @@ -386,7 +386,7 @@ impl Dialog { .map(self.mapper) } - pub fn window_id(&self) -> window::Id { + pub const fn window_id(&self) -> window::Id { self.cosmic.app.flags.window_id } } @@ -452,24 +452,24 @@ enum Message { } impl From for Message { - fn from(app_message: AppMessage) -> Message { + fn from(app_message: AppMessage) -> Self { match app_message { - AppMessage::None => Message::None, - AppMessage::Preview(_entity_opt) => Message::Preview, - AppMessage::SearchActivate => Message::SearchActivate, - AppMessage::ScrollTab(scroll_speed) => Message::ScrollTab(scroll_speed), - AppMessage::TabMessage(_entity_opt, tab_message) => Message::TabMessage(tab_message), - AppMessage::TabView(_entity_opt, view) => Message::TabView(view), - AppMessage::ToggleFoldersFirst => Message::ToggleFoldersFirst, - AppMessage::ToggleShowHidden => Message::ToggleShowHidden, - AppMessage::ZoomDefault(_entity_opt) => Message::ZoomDefault, - AppMessage::ZoomIn(_entity_opt) => Message::ZoomIn, - AppMessage::ZoomOut(_entity_opt) => Message::ZoomOut, - AppMessage::NewItem(_entity_opt, true) => Message::NewFolder, - AppMessage::Surface(action) => Message::Surface(action), + AppMessage::None => Self::None, + AppMessage::Preview(_entity_opt) => Self::Preview, + AppMessage::SearchActivate => Self::SearchActivate, + AppMessage::ScrollTab(scroll_speed) => Self::ScrollTab(scroll_speed), + AppMessage::TabMessage(_entity_opt, tab_message) => Self::TabMessage(tab_message), + AppMessage::TabView(_entity_opt, view) => Self::TabView(view), + AppMessage::ToggleFoldersFirst => Self::ToggleFoldersFirst, + AppMessage::ToggleShowHidden => Self::ToggleShowHidden, + AppMessage::ZoomDefault(_entity_opt) => Self::ZoomDefault, + AppMessage::ZoomIn(_entity_opt) => Self::ZoomIn, + AppMessage::ZoomOut(_entity_opt) => Self::ZoomOut, + AppMessage::NewItem(_entity_opt, true) => Self::NewFolder, + AppMessage::Surface(action) => Self::Surface(action), unsupported => { log::warn!("{unsupported:?} not supported in dialog mode"); - Message::None + Self::None } } } @@ -550,7 +550,7 @@ impl App { } let mut row = widget::row::with_capacity( - if !self.filters.is_empty() { 1 } else { 0 } + usize::from(!self.filters.is_empty()) + self.choices.len() * 2 + if is_condensed { 0 } else { 3 }, ) @@ -566,9 +566,10 @@ impl App { for (choice_i, choice) in self.choices.iter().enumerate() { match choice { DialogChoice::CheckBox { label, value, .. } => { - row = row.push(widget::checkbox(label, *value).on_toggle(move |checked| { - Message::Choice(choice_i, if checked { 1 } else { 0 }) - })); + row = + row.push(widget::checkbox(label, *value).on_toggle(move |checked| { + Message::Choice(choice_i, usize::from(checked)) + })); } DialogChoice::ComboBox { label, @@ -595,7 +596,7 @@ impl App { let mut has_selected = false; if let Some(items) = self.tab.items_opt() { - for item in items.iter() { + for item in items { if item.selected { has_selected = true; break; @@ -641,7 +642,7 @@ impl App { } PreviewKind::Location(location) => { if let Some(items) = self.tab.items_opt() { - for item in items.iter() { + for item in items { if item.location_opt.as_ref() == Some(location) { children.push(item.preview_view(None, military_time)); // Only show one property view to avoid issues like hangs when generating @@ -653,7 +654,7 @@ impl App { } PreviewKind::Selected => { if let Some(items) = self.tab.items_opt() { - for item in items.iter() { + for item in items { if item.selected { children.push(item.preview_view(None, military_time)); // Only show one property view to avoid issues like hangs when generating @@ -686,7 +687,7 @@ impl App { let mounter_paths: Vec<_> = mounter_items .iter() .flat_map(|item| item.1.iter()) - .filter_map(|item| item.path()) + .filter_map(MounterItem::path) .collect(); if !mounter_paths.is_empty() { for item in &mut items { @@ -703,7 +704,7 @@ impl App { )) } Err(err) => { - log::warn!("failed to rescan: {}", err); + log::warn!("failed to rescan: {err}"); cosmic::action::none() } } @@ -724,7 +725,7 @@ impl App { Some(term) => self.tab.location.path_opt().map(|path| { ( Location::Search( - path.to_path_buf(), + path.clone(), term, self.tab.config.show_hidden, Instant::now(), @@ -733,7 +734,7 @@ impl App { ) }), None => match &self.tab.location { - Location::Search(path, ..) => Some((Location::Path(path.to_path_buf()), false)), + Location::Search(path, ..) => Some((Location::Path(path.clone()), false)), _ => None, }, }; @@ -763,24 +764,21 @@ impl App { fn with_dialog_config(&mut self, f: F) -> Task { let mut dialog = self.flags.config.dialog; f(&mut dialog); - if dialog != self.flags.config.dialog { - match &self.flags.config_handler { - Some(config_handler) => { - match self.flags.config.set_dialog(config_handler, dialog) { - Ok(_) => {} - Err(err) => { - log::warn!("failed to save config \"dialog\": {}", err); - } + if dialog == self.flags.config.dialog { + Task::none() + } else { + if let Some(config_handler) = &self.flags.config_handler { + match self.flags.config.set_dialog(config_handler, dialog) { + Ok(_) => {} + Err(err) => { + log::warn!("failed to save config \"dialog\": {err}"); } } - None => { - self.flags.config.dialog = dialog; - log::warn!("failed to save config \"dialog\": no config handler",); - } + } else { + self.flags.config.dialog = dialog; + log::warn!("failed to save config \"dialog\": no config handler",); } self.update_config() - } else { - Task::none() } } @@ -788,8 +786,7 @@ impl App { let nav_bar_id = self.nav_model.iter().find(|&id| { self.nav_model .data::(id) - .map(|l| l == location) - .unwrap_or_default() + .is_some_and(|l| l == location) }); if let Some(id) = nav_bar_id { @@ -809,7 +806,7 @@ impl App { .data(Location::Recents) }); - for favorite in self.flags.config.favorites.iter() { + for favorite in &self.flags.config.favorites { if let Some(path) = favorite.path_opt() { let name = if matches!(favorite, Favorite::Home) { fl!("home") @@ -837,8 +834,8 @@ impl App { // Collect all mounter items let mut nav_items = Vec::new(); - for (key, items) in self.mounter_items.iter() { - for item in items.iter() { + for (key, items) in &self.mounter_items { + for item in items { nav_items.push((*key, item)); } } @@ -849,7 +846,7 @@ impl App { nav_model = nav_model.insert(|mut b| { b = b.text(item.name()).data(MounterData(key, item.clone())); if let Some(path) = item.path() { - b = b.data(Location::Path(path.clone())); + b = b.data(Location::Path(path)); } if let Some(icon) = item.icon(true) { b = b.icon(widget::icon::icon(icon).size(16)); @@ -878,33 +875,33 @@ impl App { if let Some((mut watcher, old_paths)) = self.watcher_opt.take() { let mut new_paths = FxHashSet::default(); if let Some(path) = &self.tab.location.path_opt() { - new_paths.insert(path.to_path_buf()); + new_paths.insert((*path).clone()); } // Unwatch paths no longer used - for path in old_paths.iter() { + for path in &old_paths { if !new_paths.contains(path) { match watcher.unwatch(path) { Ok(()) => { - log::debug!("unwatching {:?}", path); + log::debug!("unwatching {}", path.display()); } Err(err) => { - log::debug!("failed to unwatch {:?}: {}", path, err); + log::debug!("failed to unwatch {}: {}", path.display(), err); } } } } // Watch new paths - for path in new_paths.iter() { + for path in &new_paths { if !old_paths.contains(path) { //TODO: should this be recursive? match watcher.watch(path, notify::RecursiveMode::NonRecursive) { Ok(()) => { - log::debug!("watching {:?}", path); + log::debug!("watching {}", path.display()); } Err(err) => { - log::debug!("failed to watch {:?}: {}", path, err); + log::debug!("failed to watch {}: {}", path.display(), err); } } } @@ -951,7 +948,7 @@ impl Application for App { let accept_label = flags.kind.accept_label(); let location = Location::Path(match &flags.path_opt { - Some(path) => path.to_path_buf(), + Some(path) => path.clone(), None => match env::current_dir() { Ok(path) => path, Err(_) => home_dir(), @@ -972,7 +969,7 @@ impl Application for App { let key_binds = key_binds(&tab.mode); - let mut app = App { + let mut app = Self { core, flags, title, @@ -1015,16 +1012,16 @@ impl Application for App { ContextPage::Preview(_, kind) => { let mut actions = Vec::with_capacity(3); if let Some(items) = self.tab.items_opt() { - for item in items.iter() { + for item in items { if item.selected { actions.extend( item.preview_header() .into_iter() .map(|element| element.map(Message::TabMessage)), - ) + ); } } - }; + } Some( context_drawer::context_drawer( self.preview(kind).map(Message::TabMessage), @@ -1111,9 +1108,7 @@ impl Application for App { name, }) }) - .on_submit_maybe( - complete_maybe.clone().map(|maybe| move |_| maybe.clone()), - ) + .on_submit_maybe(complete_maybe.map(|maybe| move |_| maybe.clone())) .into(), ]) .spacing(space_xxs), @@ -1237,7 +1232,7 @@ impl Application for App { if let Some(mounter) = MOUNTERS.get(&data.0) { return mounter .mount(data.1.clone()) - .map(|_| cosmic::action::none()); + .map(|()| cosmic::action::none()); } } Task::none() @@ -1328,12 +1323,12 @@ impl Application for App { Ok(()) => { // cd to directory let message = Message::TabMessage(tab::Message::Location( - Location::Path(path.clone()), + Location::Path(path), )); return self.update(message); } Err(err) => { - log::warn!("failed to create {:?}: {}", path, err); + log::warn!("failed to create {}: {}", path.display(), err); } } } @@ -1366,7 +1361,7 @@ impl Application for App { return self.rescan_tab(None); } Message::Key(modifiers, key, text) => { - for (key_bind, action) in self.key_binds.iter() { + for (key_bind, action) in &self.key_binds { if key_bind.matches(modifiers, &key) { return self.update(Message::from(action.message())); } @@ -1423,11 +1418,11 @@ impl Application for App { // Check for unmounted folders let mut unmounted = Vec::new(); if let Some(old_items) = self.mounter_items.get(&mounter_key) { - for old_item in old_items.iter() { + for old_item in old_items { if let Some(old_path) = old_item.path() { if old_item.is_mounted() { let mut still_mounted = false; - for item in mounter_items.iter() { + for item in &mounter_items { if let Some(path) = item.path() { if path == old_path && item.is_mounted() { still_mounted = true; @@ -1466,61 +1461,56 @@ impl Application for App { Message::NewFolder => { if let Some(path) = self.tab.location.path_opt() { self.dialog_pages.push_back(DialogPage::NewFolder { - parent: path.to_path_buf(), + parent: path.clone(), name: String::new(), }); return widget::text_input::focus(self.dialog_text_input.clone()); } } Message::NotifyEvents(events) => { - log::debug!("{:?}", events); + log::debug!("{events:?}"); if let Some(path) = self.tab.location.path_opt() { let mut contains_change = false; - for event in events.iter() { - for event_path in event.paths.iter() { + for event in &events { + for event_path in &event.paths { if event_path.starts_with(path) { - match event.kind { - notify::EventKind::Modify( - notify::event::ModifyKind::Metadata(_), - ) - | notify::EventKind::Modify(notify::event::ModifyKind::Data( - _, - )) => { - // If metadata or data changed, find the matching item and reload it - //TODO: this could be further optimized by looking at what exactly changed - if let Some(items) = &mut self.tab.items_opt { - for item in items.iter_mut() { - if item.path_opt() == Some(event_path) { - //TODO: reload more, like mime types? - match fs::metadata(event_path) { - Ok(new_metadata) => { - if let ItemMetadata::Path { - metadata, - .. - } = &mut item.metadata - { - *metadata = new_metadata; - } - } - Err(err) => { - log::warn!( - "failed to reload metadata for {:?}: {}", - path, - err - ); + if let notify::EventKind::Modify( + notify::event::ModifyKind::Metadata(_) + | notify::event::ModifyKind::Data(_), + ) = event.kind + { + // If metadata or data changed, find the matching item and reload it + //TODO: this could be further optimized by looking at what exactly changed + if let Some(items) = &mut self.tab.items_opt { + for item in items.iter_mut() { + if item.path_opt() == Some(event_path) { + //TODO: reload more, like mime types? + match fs::metadata(event_path) { + Ok(new_metadata) => { + if let ItemMetadata::Path { + metadata, .. + } = &mut item.metadata + { + *metadata = new_metadata; } } - //TODO item.thumbnail_opt = + Err(err) => { + log::warn!( + "failed to reload metadata for {}: {}", + path.display(), + err + ); + } } + //TODO item.thumbnail_opt = } } } - _ => { - // Any other events reload the whole tab - contains_change = true; - break; - } + } else { + // Any other events reload the whole tab + contains_change = true; + break; } } } @@ -1543,13 +1533,13 @@ impl Application for App { Message::Open => { let mut paths = Vec::new(); if let Some(items) = self.tab.items_opt() { - for item in items.iter() { + for item in items { if item.selected { if let Some(path) = item.path_opt() { paths.push(path.clone()); let _ = update_recently_used( &path.clone(), - App::APP_ID.to_string(), + Self::APP_ID.to_string(), "cosmic-files".to_string(), None, ); @@ -1560,7 +1550,7 @@ impl Application for App { // Ensure selection is allowed //TODO: improve tab logic so this doesn't block the open button so often - for path in paths.iter() { + for path in &paths { let path_is_dir = path.is_dir(); if path_is_dir != self.flags.kind.is_dir() { if path_is_dir && paths.len() == 1 { @@ -1569,10 +1559,10 @@ impl Application for App { Location::Path(path.clone()), )); return self.update(message); - } else { - // Otherwise, this is not a legal selection - return Task::none(); } + + // Otherwise, this is not a legal selection + return Task::none(); } } @@ -1604,7 +1594,7 @@ impl Application for App { if path.is_dir() { // cd to directory let message = Message::TabMessage(tab::Message::Location( - Location::Path(path.clone()), + Location::Path(path), )); return self.update(message); } else if !replace && path.exists() { @@ -1612,17 +1602,16 @@ impl Application for App { filename: filename.clone(), }); return widget::button::focus(REPLACE_BUTTON_ID.clone()); - } else { - self.result_opt = Some(DialogResult::Open(vec![path])); - return window::close(self.flags.window_id); } + self.result_opt = Some(DialogResult::Open(vec![path])); + return window::close(self.flags.window_id); } } } } Message::ScrollTab(scroll_speed) => { return self.update(Message::TabMessage(tab::Message::ScrollTab( - (scroll_speed as f32) / 10.0, + f32::from(scroll_speed) / 10.0, ))); } Message::SearchActivate => { @@ -1652,7 +1641,7 @@ impl Application for App { if let Some(items) = self.tab.items_opt() { if let Some(item) = items.get(click_i) { if item.selected && !item.metadata.is_dir() { - *filename = item.name.clone(); + filename.clone_from(&item.name); } } } @@ -1689,7 +1678,7 @@ impl Application for App { let autosize_id = widget::Id::unique(); commands.push(self.update(Message::Surface( cosmic::surface::action::app_popup( - move |app: &mut App| -> SctkPopupSettings { + move |app: &mut Self| -> SctkPopupSettings { let anchor_rect = Rectangle { x: point.x as i32, y: point.y as i32, @@ -1715,7 +1704,7 @@ impl Application for App { input_zone: None, } }, - Some(Box::new(move |app: &App| { + Some(Box::new(move |app: &Self| { widget::autosize::autosize( menu::context_menu( &app.tab, @@ -1789,17 +1778,13 @@ impl Application for App { // Parse filters let mut parsed_globs = Vec::new(); let mut parsed_mimes = Vec::new(); - for pattern in filter.patterns.iter() { + for pattern in &filter.patterns { match pattern { DialogFilterPattern::Glob(value) => { match glob::Pattern::new(value) { Ok(glob) => parsed_globs.push(glob), Err(err) => { - log::warn!( - "failed to parse glob {:?}: {}", - value, - err - ); + log::warn!("failed to parse glob {value:?}: {err}"); } } } @@ -1807,11 +1792,7 @@ impl Application for App { match mime_guess::Mime::from_str(value) { Ok(mime) => parsed_mimes.push(mime), Err(err) => { - log::warn!( - "failed to parse mime {:?}: {}", - value, - err - ); + log::warn!("failed to parse mime {value:?}: {err}"); } } } @@ -1825,14 +1806,14 @@ impl Application for App { } // Check for mime type match (first because it is faster) - for mime in parsed_mimes.iter() { + for mime in &parsed_mimes { if mime == &item.mime { return true; } } // Check for glob match (last because it is slower) - for glob in parsed_globs.iter() { + for glob in &parsed_globs { if glob.matches(&item.name) { return true; } @@ -1846,7 +1827,7 @@ impl Application for App { // Select based on filename if let DialogKind::SaveFile { filename } = &self.flags.kind { - for item in items.iter_mut() { + for item in &mut items { item.selected = &item.name == filename; } } @@ -1864,9 +1845,8 @@ impl Application for App { // Reset focus on location change if self.search_get().is_some() { return widget::text_input::focus(self.search_id.clone()); - } else { - return widget::text_input::focus(self.filename_id.clone()); } + return widget::text_input::focus(self.filename_id.clone()); } } Message::TabView(view) => { @@ -1959,7 +1939,7 @@ impl Application for App { .on_input(Message::SearchInput), ) .padding(space_xxs), - ) + ); } } @@ -2055,15 +2035,14 @@ impl Application for App { Ok(()) => {} Err(err) => { log::warn!( - "failed to send notify events: {:?}", - err + "failed to send notify events: {err:?}" ); } } } } Err(err) => { - log::warn!("failed to watch files: {:?}", err); + log::warn!("failed to watch files: {err:?}"); } } }, @@ -2080,12 +2059,12 @@ impl Application for App { { Ok(()) => {} Err(err) => { - log::warn!("failed to send notify watcher: {:?}", err); + log::warn!("failed to send notify watcher: {err:?}"); } } } Err(err) => { - log::warn!("failed to create file watcher: {:?}", err); + log::warn!("failed to create file watcher: {err:?}"); } } @@ -2113,15 +2092,17 @@ impl Application for App { for (key, mounter) in MOUNTERS.iter() { subscriptions.push( - mounter.subscription().with(*key).map( - |(key, mounter_message)| match mounter_message { - MounterMessage::Items(items) => Message::MounterItems(key, items), - _ => { - log::warn!("{:?} not supported in dialog mode", mounter_message); + mounter + .subscription() + .with(*key) + .map(|(key, mounter_message)| { + if let MounterMessage::Items(items) = mounter_message { + Message::MounterItems(key, items) + } else { + log::warn!("{mounter_message:?} not supported in dialog mode"); Message::None } - }, - ), + }), ); } diff --git a/src/lib.rs b/src/lib.rs index fe3aae8..9b18488 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,24 +34,28 @@ pub(crate) fn err_str(err: T) -> String { } pub fn desktop_dir() -> PathBuf { - match dirs::desktop_dir() { - Some(path) => path, - None => { - let path = home_dir().join("Desktop"); - log::warn!("failed to locate desktop directory, falling back to {path:?}"); - path - } + if let Some(path) = dirs::desktop_dir() { + path + } else { + let path = home_dir().join("Desktop"); + log::warn!( + "failed to locate desktop directory, falling back to {}", + path.display() + ); + path } } pub fn home_dir() -> PathBuf { - match dirs::home_dir() { - Some(home) => home, - None => { - let path = PathBuf::from("/"); - log::warn!("failed to locate home directory, falling back to {path:?}"); - path - } + if let Some(home) = dirs::home_dir() { + home + } else { + let path = PathBuf::from("/"); + log::warn!( + "failed to locate home directory, falling back to {}", + path.display() + ); + path } } @@ -123,12 +127,9 @@ pub fn main() -> Result<(), Box> { } else { //TODO: support more URLs let path = match url::Url::parse(&arg) { - Ok(url) if url.scheme() == "file" => match url.to_file_path() { - Ok(path) => path, - Err(()) => { - log::warn!("invalid argument {:?}", arg); - continue; - } + Ok(url) if url.scheme() == "file" => if let Ok(path) = url.to_file_path() { path } else { + log::warn!("invalid argument {arg:?}"); + continue; }, Ok(url) => { uris.push(url); @@ -139,7 +140,7 @@ pub fn main() -> Result<(), Box> { match fs::canonicalize(&path) { Ok(absolute) => Location::Path(absolute), Err(err) => { - log::warn!("failed to canonicalize {:?}: {}", path, err); + log::warn!("failed to canonicalize {}: {}", path.display(), err); continue; } } @@ -153,7 +154,7 @@ pub fn main() -> Result<(), Box> { Ok(fork::Fork::Child) => (), Ok(fork::Fork::Parent(_child_pid)) => process::exit(0), Err(err) => { - eprintln!("failed to daemonize: {:?}", err); + eprintln!("failed to daemonize: {err:?}"); process::exit(1); } } diff --git a/src/localize.rs b/src/localize.rs index 2f8fe17..1eb11ee 100644 --- a/src/localize.rs +++ b/src/localize.rs @@ -94,6 +94,6 @@ pub fn localize() { let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages(); if let Err(error) = localizer.select(&requested_languages) { - eprintln!("Error while loading language for COSMIC Files {}", error); + eprintln!("Error while loading language for COSMIC Files {error}"); } } diff --git a/src/menu.rs b/src/menu.rs index bb5f3a4..db16472 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -43,7 +43,7 @@ macro_rules! menu_button { ); } -fn menu_button_optional( +const fn menu_button_optional( label: String, action: Action, enabled: bool, @@ -61,7 +61,7 @@ pub fn context_menu<'a>( modifiers: &Modifiers, ) -> Element<'a, tab::Message> { let find_key = |action: &Action| -> String { - for (key_bind, key_action) in key_binds.iter() { + for (key_bind, key_action) in key_binds { if action == key_action { return key_bind.to_string(); } @@ -110,11 +110,11 @@ pub fn context_menu<'a>( let mut selected_types: Vec = vec![]; let mut selected_mount_point = 0; if let Some(items) = tab.items_opt() { - for item in items.iter() { + for item in items { if item.selected { selected += 1; if item.metadata.is_dir() { - selected_mount_point += item.is_mount_point as i32; + selected_mount_point += i32::from(item.is_mount_point); selected_dir += 1; } match &item.location_opt { @@ -131,7 +131,7 @@ pub fn context_menu<'a>( selected_types.push(item.mime.clone()); } } - }; + } selected_types.sort_unstable(); selected_types.dedup(); selected_trash_only = selected_trash_only && selected == 1; @@ -168,7 +168,7 @@ pub fn context_menu<'a>( #[cfg(feature = "desktop")] { for (i, action) in entry.desktop_actions.into_iter().enumerate() { - children.push(menu_item(action.name, Action::ExecEntryAction(i)).into()) + children.push(menu_item(action.name, Action::ExecEntryAction(i)).into()); } } children.push(divider::horizontal::light().into()); @@ -396,12 +396,12 @@ pub fn dialog_menu( let mut selected_gallery = 0; if let Some(items) = tab.items_opt() { - for item in items.iter() { + for item in items { if item.selected && item.can_gallery() { selected_gallery += 1; } } - }; + } MenuBar::new(vec![ menu::Tree::with_children( @@ -530,7 +530,7 @@ pub fn menu_bar<'a>( modifiers: &Modifiers, key_binds: &HashMap, ) -> Element<'a, Message> { - let sort_options = tab_opt.map(|tab| tab.sort_options()); + let sort_options = tab_opt.map(Tab::sort_options); let sort_item = |label, sort, dir| { menu::Item::CheckBox( label, @@ -547,7 +547,7 @@ pub fn menu_bar<'a>( let mut selected = 0; let mut selected_gallery = 0; if let Some(items) = tab_opt.and_then(|tab| tab.items_opt()) { - for item in items.iter() { + for item in items { if item.selected { selected += 1; if item.metadata.is_dir() { @@ -558,7 +558,7 @@ pub fn menu_bar<'a>( } } } - }; + } let (delete_item, delete_item_action) = if in_trash || modifiers.shift() { (fl!("delete-permanently"), Action::Delete) diff --git a/src/mime_app.rs b/src/mime_app.rs index c582c5e..fba710b 100644 --- a/src/mime_app.rs +++ b/src/mime_app.rs @@ -54,7 +54,7 @@ pub fn exec_to_command( // Number of args before the field code. // This won't be an off by one err below because take is not zero indexed. let field_code_pos = field_code_pos.unwrap_or_default(); - let mut processes = match args_handler.map(|s| s.as_str()) { + let mut processes = match args_handler.map(String::as_str) { Some("%f") => { let mut processes = Vec::with_capacity(path_opt.len()); @@ -137,7 +137,7 @@ pub fn exec_to_command( if !EXEC_HANDLERS.contains(&field_code) && !DEPRECATED_HANDLERS.contains(&field_code) { - log::warn!("unsupported Exec code {:?} in {:?}", field_code, exec); + log::warn!("unsupported Exec code {field_code:?} in {exec:?}"); return None; } } @@ -215,8 +215,7 @@ fn filename_eq(path_opt: &Option, filename: &str) -> bool { path_opt .as_ref() .and_then(|path| path.file_name()) - .map(|x| x == filename) - .unwrap_or(false) + .is_some_and(|x| x == filename) } pub struct MimeAppCache { @@ -259,12 +258,12 @@ impl MimeAppCache { // Load desktop applications by supported mime types //TODO: hashmap for all apps by id? let all_apps: Vec<_> = desktop::load_applications(locale, false, None).collect(); - for app in all_apps.iter() { + for app in &all_apps { //TODO: just collect apps that can be executed with a file argument? if !app.mime_types.is_empty() { self.apps.push(MimeApp::from(app)); } - for mime in app.mime_types.iter() { + for mime in &app.mime_types { let apps = self .cache .entry(mime.clone()) @@ -273,7 +272,7 @@ impl MimeAppCache { apps.push(MimeApp::from(app)); } } - for category in app.categories.iter() { + for category in &app.categories { if category == "TerminalEmulator" { self.terminals.push(MimeApp::from(app)); break; @@ -284,7 +283,7 @@ impl MimeAppCache { let desktops: Vec = env::var("XDG_CURRENT_DESKTOP") .unwrap_or_default() .split(':') - .map(|x| x.to_ascii_lowercase()) + .map(str::to_ascii_lowercase) .collect(); // Load mimeapps.list files @@ -315,7 +314,7 @@ impl MimeAppCache { let entry = match freedesktop_entry_parser::parse_entry(&path) { Ok(ok) => ok, Err(err) => { - log::warn!("failed to parse {:?}: {}", path, err); + log::warn!("failed to parse {}: {}", path.display(), err); continue; } }; @@ -328,7 +327,7 @@ impl MimeAppCache { if let Ok(mime) = attr.name.parse::() { if let Some(filenames) = attr.value { for filename in filenames.split_terminator(';') { - log::trace!("add {}={}", mime, filename); + log::trace!("add {mime}={filename}"); let apps = self .cache .entry(mime.clone()) @@ -340,9 +339,7 @@ impl MimeAppCache { apps.push(MimeApp::from(app)); } else { log::info!( - "failed to add association for {:?}: application {:?} not found", - mime, - filename + "failed to add association for {mime:?}: application {filename:?} not found" ); } } @@ -355,7 +352,7 @@ impl MimeAppCache { if let Ok(mime) = attr.name.parse::() { if let Some(filenames) = attr.value { for filename in filenames.split_terminator(';') { - log::trace!("remove {}={}", mime, filename); + log::trace!("remove {mime}={filename}"); if let Some(apps) = self.cache.get_mut(&mime) { apps.retain(|x| !filename_eq(&x.path, filename)); } @@ -368,7 +365,7 @@ impl MimeAppCache { if let Ok(mime) = attr.name.parse::() { if let Some(filenames) = attr.value { for filename in filenames.split_terminator(';') { - log::trace!("default {}={}", mime, filename); + log::trace!("default {mime}={filename}"); if let Some(apps) = self.cache.get_mut(&mime) { let mut found = false; for app in apps.iter_mut() { @@ -381,13 +378,10 @@ impl MimeAppCache { } if found { break; - } else { - log::debug!( - "failed to set default for {:?}: application {:?} not found", - mime, - filename - ); } + log::debug!( + "failed to set default for {mime:?}: application {filename:?} not found" + ); } } } @@ -412,7 +406,7 @@ impl MimeAppCache { // Copy icons to special cache //TODO: adjust dropdown API so this is no longer needed - for (mime, apps) in self.cache.iter() { + for (mime, apps) in &self.cache { self.icons.insert( mime.clone(), apps.iter().map(|app| app.icon.clone()).collect(), @@ -420,7 +414,7 @@ impl MimeAppCache { } let elapsed = start.elapsed(); - log::info!("loaded mime app cache in {:?}", elapsed); + log::info!("loaded mime app cache in {elapsed:?}"); } pub fn apps(&self) -> &[MimeApp] { @@ -428,13 +422,11 @@ impl MimeAppCache { } pub fn get(&self, key: &Mime) -> &[MimeApp] { - static EMPTY: Vec = Vec::new(); - self.cache.get(key).unwrap_or(&EMPTY) + self.cache.get(key).map_or(&[], Vec::as_slice) } pub fn icons(&self, key: &Mime) -> &[widget::icon::Handle] { - static EMPTY: Vec = Vec::new(); - self.icons.get(key).unwrap_or(&EMPTY) + self.icons.get(key).map_or(&[], Vec::as_slice) } fn get_default_terminal(&self) -> Option { @@ -466,7 +458,7 @@ impl MimeAppCache { } for id in &preference_order { - for terminal in self.terminals.iter() { + for terminal in &self.terminals { if &terminal.id == id { return Some(terminal); } @@ -498,7 +490,7 @@ impl MimeAppCache { } Err(err) => { if err.kind() != io::ErrorKind::NotFound { - log::warn!("failed to read {path:?}: {err}"); + log::warn!("failed to read {}: {}", path.display(), err); return; } } @@ -517,7 +509,7 @@ impl MimeAppCache { self.reload(); } Err(err) => { - log::warn!("failed to write {path:?}: {err}"); + log::warn!("failed to write {}: {}", path.display(), err); } } } diff --git a/src/mounter/gvfs.rs b/src/mounter/gvfs.rs index 3eda468..7c84039 100644 --- a/src/mounter/gvfs.rs +++ b/src/mounter/gvfs.rs @@ -100,9 +100,10 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result, String> { let metadata = if !force_dir && !info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE) { let mtime = info.attribute_uint64(gio::FILE_ATTRIBUTE_TIME_MODIFIED); let is_dir = matches!(info.file_type(), gio::FileType::Directory); - let size_opt = match is_dir { - true => None, - false => Some(info.size() as u64), + let size_opt = if is_dir { + None + } else { + Some(info.size() as u64) }; let mut children_opt = None; @@ -114,7 +115,7 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result, String> { children_opt = Some(entries.count()); } Err(err) => { - log::warn!("failed to read directory {:?}: {}", path, err); + log::warn!("failed to read directory {}: {}", path.display(), err); children_opt = Some(0); } } @@ -287,7 +288,7 @@ impl Item { self.name.clone() } - pub fn is_mounted(&self) -> bool { + pub const fn is_mounted(&self) -> bool { self.is_mounted } @@ -395,7 +396,7 @@ impl Gvfs { continue; } - log::info!("mount {}", name); + log::info!("mount {name}"); //TODO: do not use name as a URI for mount_op let mount_op = mount_op(name.to_string(), event_tx.clone()); let event_tx = event_tx.clone(); @@ -406,7 +407,7 @@ impl Gvfs { Some(&mount_op), gio::Cancellable::NONE, move |res| { - log::info!("mount {}: result {:?}", name, res); + log::info!("mount {name}: result {res:?}"); event_tx.send(Event::MountResult(mounter_item, match res { Ok(()) => { _ = complete_tx.send(Ok(())); @@ -416,7 +417,7 @@ impl Gvfs { _ = complete_tx.send(Err(anyhow::anyhow!("{err:?}"))); match err.kind::() { Some(gio::IOErrorEnum::FailedHandled) => Ok(false), - _ => Err(format!("{}", err)) + _ => Err(format!("{err}")) }} })).unwrap(); }, @@ -433,7 +434,7 @@ impl Gvfs { Some(&mount_op), gio::Cancellable::NONE, move |res| { - log::info!("network drive {}: result {:?}", uri, res); + log::info!("network drive {uri}: result {res:?}"); event_tx.send(Event::NetworkResult(uri, match res { Ok(()) => { _ = result_tx.send(Ok(())); @@ -442,7 +443,7 @@ impl Gvfs { _ = result_tx.send(Err(anyhow::anyhow!("{err:?}"))); match err.kind::() { Some(gio::IOErrorEnum::FailedHandled) => Ok(false), - _ => Err(format!("{}", err)) + _ => Err(format!("{err}")) }} })).unwrap(); } @@ -475,7 +476,7 @@ impl Gvfs { Some(&mount_op), gio::Cancellable::NONE, move |res| { - log::info!("network scan mounted {}: result {:?}", uri, res); + log::info!("network scan mounted {uri}: result {res:?}"); // FIXME sometimes a uri can be mounted and then not recognized as mounted... // seems to be related to uri with a path items_tx.blocking_send(network_scan(&original_uri, sizes)).unwrap(); @@ -485,7 +486,7 @@ impl Gvfs { }, Err(err) => match err.kind::() { Some(gio::IOErrorEnum::FailedHandled) => Ok(false), - _ => Err(format!("{}", err)) + _ => Err(format!("{err}")) } })).unwrap(); } @@ -509,25 +510,25 @@ impl Gvfs { } if MountExt::can_eject(&mount) { - log::info!("eject {}", name); + log::info!("eject {name}"); MountExt::eject_with_operation( &mount, gio::MountUnmountFlags::NONE, gio::MountOperation::NONE, gio::Cancellable::NONE, move |result| { - log::info!("eject {}: result {:?}", name, result); + log::info!("eject {name}: result {result:?}"); }, ); } else { - log::info!("unmount {}", name); + log::info!("unmount {name}"); MountExt::unmount_with_operation( &mount, gio::MountUnmountFlags::NONE, gio::MountOperation::NONE, gio::Cancellable::NONE, move |result| { - log::info!("unmount {}: result {:?}", name, result); + log::info!("unmount {name}: result {result:?}"); }, ); } @@ -536,7 +537,7 @@ impl Gvfs { } } }); - main_loop.run() + main_loop.run(); }); Self { command_tx, @@ -615,7 +616,7 @@ impl Mounter for Gvfs { match event { Event::Changed => command_tx.send(Cmd::Rescan).unwrap(), Event::Items(items) => { - output.send(MounterMessage::Items(items)).await.unwrap() + output.send(MounterMessage::Items(items)).await.unwrap(); } Event::MountResult(item, res) => output .send(MounterMessage::MountResult(item, res)) diff --git a/src/mouse_area.rs b/src/mouse_area.rs index b365c59..ba009af 100644 --- a/src/mouse_area.rs +++ b/src/mouse_area.rs @@ -199,7 +199,7 @@ impl<'a, Message> MouseArea<'a, Message> { } #[must_use] - pub fn show_drag_rect(mut self, show_drag_rect: bool) -> Self { + pub const fn show_drag_rect(mut self, show_drag_rect: bool) -> Self { self.show_drag_rect = show_drag_rect; self } @@ -379,7 +379,7 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { - if let event::Status::Captured = self.content.as_widget_mut().on_event( + if self.content.as_widget_mut().on_event( &mut tree.children[0], event.clone(), layout, @@ -388,7 +388,8 @@ where clipboard, shell, viewport, - ) { + ) == event::Status::Captured + { return event::Status::Captured; } @@ -507,7 +508,7 @@ where Renderer: 'a + renderer::Renderer, Theme: 'a, { - fn from(area: MouseArea<'a, Message>) -> Element<'a, Message> { + fn from(area: MouseArea<'a, Message>) -> Self { Element::new(area) } } @@ -538,12 +539,12 @@ fn update( match (position_in, state.last_position) { (None, Some(_)) => { if let Some(message) = widget.on_exit.as_ref() { - shell.publish(message()) + shell.publish(message()); } } (Some(_), None) => { if let Some(message) = widget.on_enter.as_ref() { - shell.publish(message()) + shell.publish(message()); } } _ => {} @@ -632,8 +633,7 @@ fn update( let recent_click = state .prev_click .as_ref() - .map(|(_, i)| Instant::now().duration_since(*i) <= DOUBLE_CLICK_DURATION) - .unwrap_or_default(); + .is_some_and(|(_, i)| Instant::now().duration_since(*i) <= DOUBLE_CLICK_DURATION); if matches!( event, Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) @@ -653,7 +653,10 @@ fn update( } if let Some(message) = widget.on_right_press.as_ref() { - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = event { + if matches!( + event, + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) + ) { let point_opt = if widget.on_right_press_window_position { cursor.position_over(layout_bounds).map(|mut p| { p.x -= offset.x; @@ -667,14 +670,16 @@ fn update( if widget.on_right_press_no_capture { return event::Status::Ignored; - } else { - return event::Status::Captured; } + return event::Status::Captured; } } if let Some(message) = widget.on_right_release.as_ref() { - if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) = event { + if matches!( + event, + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) + ) { shell.publish(message(cursor.position_in(layout_bounds))); return event::Status::Captured; @@ -682,7 +687,10 @@ fn update( } if let Some(message) = widget.on_middle_press.as_ref() { - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) = event { + if matches!( + event, + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) + ) { shell.publish(message(cursor.position_in(layout_bounds))); return event::Status::Captured; @@ -690,7 +698,10 @@ fn update( } if let Some(message) = widget.on_middle_release.as_ref() { - if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) = event { + if matches!( + event, + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) + ) { shell.publish(message(cursor.position_in(layout_bounds))); return event::Status::Captured; @@ -698,7 +709,10 @@ fn update( } if let Some(message) = widget.on_back_press.as_ref() { - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Back)) = event { + if matches!( + event, + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Back)) + ) { shell.publish(message(cursor.position_in(layout_bounds))); return event::Status::Captured; @@ -706,7 +720,10 @@ fn update( } if let Some(message) = widget.on_back_release.as_ref() { - if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Back)) = event { + if matches!( + event, + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Back)) + ) { shell.publish(message(cursor.position_in(layout_bounds))); return event::Status::Captured; @@ -714,7 +731,10 @@ fn update( } if let Some(message) = widget.on_forward_press.as_ref() { - if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Forward)) = event { + if matches!( + event, + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Forward)) + ) { shell.publish(message(cursor.position_in(layout_bounds))); return event::Status::Captured; @@ -722,7 +742,10 @@ fn update( } if let Some(message) = widget.on_forward_release.as_ref() { - if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Forward)) = event { + if matches!( + event, + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Forward)) + ) { shell.publish(message(cursor.position_in(layout_bounds))); return event::Status::Captured; diff --git a/src/operation/controller.rs b/src/operation/controller.rs index 01eb9f3..f4b26fe 100644 --- a/src/operation/controller.rs +++ b/src/operation/controller.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, Mutex}; use tokio::sync::Notify; -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ControllerState { Cancelled, Failed, diff --git a/src/operation/mod.rs b/src/operation/mod.rs index 2eb9014..23fd18f 100644 --- a/src/operation/mod.rs +++ b/src/operation/mod.rs @@ -36,7 +36,7 @@ async fn handle_replace( let item_from = match tab::item_from_path(file_from, IconSizes::default()) { Ok(ok) => ok, Err(err) => { - log::warn!("{}", err); + log::warn!("{err}"); return ReplaceResult::Cancel; } }; @@ -44,7 +44,7 @@ async fn handle_replace( let item_to = match tab::item_from_path(file_to, IconSizes::default()) { Ok(ok) => ok, Err(err) => { - log::warn!("{}", err); + log::warn!("{err}"); return ReplaceResult::Cancel; } }; @@ -98,13 +98,13 @@ async fn copy_or_move( compio::runtime::spawn(async move { let controller = controller_c; log::info!( - "{} {:?} to {:?}", + "{} {:?} to {}", match method { Method::Copy => "Copy", Method::Move { .. } => "Move", }, paths, - to + to.display() ); // Handle duplicate file names by renaming paths @@ -141,12 +141,15 @@ async fn copy_or_move( //TODO: use compio::fs::rename? match fs::rename(from, to) { Ok(()) => { - log::info!("renamed {from:?} to {to:?}"); + log::info!("renamed {} to {}", from.display(), to.display()); false } Err(err) => { log::info!( - "failed to rename {from:?} to {to:?}, fallback to recursive move: {err}" + "failed to rename {} to {}, fallback to recursive move: {}", + from.display(), + to.display(), + err ); true } @@ -227,15 +230,14 @@ fn copy_unique_path(from: &Path, to: &Path) -> PathBuf { .unwrap_or_else(|| { from.file_stem() .and_then(|s| s.to_str()) - .map(|stem| { + .map_or((file_name, None), |stem| { ( stem.to_string(), from.extension() .and_then(|e| e.to_str()) - .map(|e| e.to_string()), + .map(str::to_string), ) }) - .unwrap_or((file_name, None)) }) }; @@ -283,7 +285,7 @@ fn paths_parent_name(paths: &[PathBuf]) -> Cow<'_, str> { return fl!("unknown-folder").into(); }; - for path in paths.iter() { + for path in paths { //TODO: is it possible to have different parents, and what should be returned? if path.parent() != Some(parent) { return fl!("unknown-folder").into(); @@ -397,18 +399,18 @@ impl OperationError { pub fn from_err(err: T, controller: &Controller) -> Self { controller.set_state(ControllerState::Failed); - OperationError { + Self { kind: OperationErrorType::Generic(err.to_string()), } } pub fn from_kind(kind: OperationErrorType, controller: &Controller) -> Self { controller.set_state(ControllerState::Failed); - OperationError { kind } + Self { kind } } pub fn from_msg(m: impl Into) -> Self { - OperationError { + Self { kind: OperationErrorType::Generic(m.into()), } } @@ -572,7 +574,7 @@ impl Operation { } } - pub fn show_progress_notification(&self) -> bool { + pub const fn show_progress_notification(&self) -> bool { // Long running operations show a progress notification match self { Self::Compress { .. } @@ -625,7 +627,7 @@ impl Operation { let controller = controller_c; let Some(relative_root) = to.parent() else { return Err(OperationError::from_err( - format!("path {:?} has no parent directory", to), + format!("path {} has no parent directory", to.display()), &controller, )); }; @@ -636,7 +638,7 @@ impl Operation { }; let mut paths = paths; - for path in paths.clone().iter() { + for path in &paths.clone() { if path.is_dir() { let new_paths_it = WalkDir::new(path).into_iter(); for entry in new_paths_it.skip(1) { @@ -1011,7 +1013,7 @@ impl Operation { } Self::RemoveFromRecents { paths } => { tokio::task::spawn_blocking(move || { - let path_refs = paths.iter().map(|p| p.as_ref()).collect::>(); + let path_refs = paths.iter().map(PathBuf::as_path).collect::>(); recently_used_xbel::remove_recently_used(&path_refs) }) .await @@ -1205,7 +1207,7 @@ mod tests { debug!("[{id}] Replace request"); tx.send(ReplaceResult::Cancel) .await - .expect("Sending a response to a replace request should succeed") + .expect("Sending a response to a replace request should succeed"); } _ => unreachable!( "Only [ `Message::PendingProgress`, `Message::DialogPush(DialogPage::Replace)` ] are sent from operation" diff --git a/src/operation/recursive.rs b/src/operation/recursive.rs index 6802459..c6caac5 100644 --- a/src/operation/recursive.rs +++ b/src/operation/recursive.rs @@ -68,7 +68,7 @@ impl Context { continue; } - for entry in WalkDir::new(&from_parent).into_iter() { + for entry in WalkDir::new(&from_parent) { self.controller .check() .await @@ -76,7 +76,11 @@ impl Context { let entry = entry.map_err(|err| { OperationError::from_err( - format!("failed to walk directory {:?}: {}", from_parent, err), + format!( + "failed to walk directory {}: {}", + from_parent.display(), + err + ), &self.controller, ) })?; @@ -92,7 +96,7 @@ impl Context { } else if file_type.is_symlink() { let target = fs::read_link(&from).map_err(|err| { OperationError::from_err( - format!("failed to read link {:?}: {}", from, err), + format!("failed to read link {}: {}", from_parent.display(), err), &self.controller, ) })?; @@ -111,8 +115,10 @@ impl Context { let relative = from.strip_prefix(&from_parent).map_err(|err| { OperationError::from_err( format!( - "failed to remove prefix {:?} from {:?}: {}", - from_parent, from, err + "failed to remove prefix {} from {}: {}", + from_parent.display(), + from.display(), + err ), &self.controller, ) @@ -163,8 +169,11 @@ impl Context { if op.run(self, progress).await.map_err(|err| { OperationError::from_err( format!( - "failed to {:?} {:?} to {:?}: {}", - op.kind, op.from, op.to, err + "failed to {:?} {} to {}: {}", + op.kind, + op.from.display(), + op.to.display(), + err ), &self.controller, ) @@ -209,7 +218,7 @@ impl Context { } ReplaceResult::KeepBoth => match op.to.parent() { Some(to_parent) => Ok(ControlFlow::Continue(copy_unique_path(&op.from, to_parent))), - None => Err(format!("failed to get parent of {:?}", op.to).into()), + None => Err(format!("failed to get parent of {}", op.to.display()).into()), }, ReplaceResult::Skip(apply_to_all) => { if apply_to_all { @@ -319,7 +328,11 @@ impl Op { (ctx.on_progress)(self, &progress); if let Err(err) = to_file.set_permissions(metadata.permissions()).await { // This error is not propagated upwards as some filesystems do not support setting permissions - log::warn!("failed to set permissions for {:?}: {}", self.to, err); + log::warn!( + "failed to set permissions for {}: {}", + self.to.display(), + err + ); } // Prevent spamming the progress callbacks. @@ -402,7 +415,7 @@ impl Op { self.skipped.cleanup.set(true); } // Try standard copy if hard link fails with cross device error - let mut copy_op = Op { + let mut copy_op = Self { kind: OpKind::Copy, from: self.from.clone(), to: self.to.clone(), @@ -410,9 +423,8 @@ impl Op { is_cleanup: self.is_cleanup, }; return Box::pin(copy_op.run(ctx, progress)).await; - } else { - return Err(err.into()); } + return Err(err.into()); } } } diff --git a/src/tab.rs b/src/tab.rs index e518a7e..c07a28e 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -308,13 +308,13 @@ fn tab_complete(path: &Path) -> Result, Box> { return Ok(Vec::new()); } else { path.parent() - .ok_or_else(|| format!("path has no parent {:?}", path))? + .ok_or_else(|| format!("path has no parent {}", path.display()))? }; let child_os = path.strip_prefix(parent)?; let child = child_os .to_str() - .ok_or_else(|| format!("invalid UTF-8 {:?}", child_os))?; + .ok_or_else(|| format!("invalid UTF-8 {}", child_os.display()))?; let pattern = format!("^{}", regex::escape(child)); let regex = regex::RegexBuilder::new(&pattern) @@ -353,20 +353,20 @@ pub fn trash_entries() -> usize { } pub fn trash_icon(icon_size: u16) -> widget::icon::Handle { - widget::icon::from_name(if !trash::os_limited::is_empty().unwrap_or(true) { - "user-trash-full" - } else { + widget::icon::from_name(if trash::os_limited::is_empty().unwrap_or(true) { "user-trash" + } else { + "user-trash-full" }) .size(icon_size) .handle() } pub fn trash_icon_symbolic(icon_size: u16) -> widget::icon::Handle { - widget::icon::from_name(if !trash::os_limited::is_empty().unwrap_or(true) { - "user-trash-full-symbolic" - } else { + widget::icon::from_name(if trash::os_limited::is_empty().unwrap_or(true) { "user-trash-symbolic" + } else { + "user-trash-full-symbolic" }) .size(icon_size) .handle() @@ -388,7 +388,7 @@ fn format_size(size: u64) -> String { } else if size >= KB { format!("{:.1} KB", size as f64 / KB as f64) } else { - format!("{} B", size) + format!("{size} B") } } @@ -396,7 +396,7 @@ const MODE_SHIFT_USER: u32 = 6; const MODE_SHIFT_GROUP: u32 = 3; const MODE_SHIFT_OTHER: u32 = 0; -fn get_mode_part(mode: u32, shift: u32) -> u32 { +const fn get_mode_part(mode: u32, shift: u32) -> u32 { (mode >> shift) & 0o7 } @@ -524,7 +524,7 @@ fn hidden_attribute(metadata: &Metadata) -> bool { metadata.file_attributes() & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum FsKind { Local, Remote, @@ -580,7 +580,7 @@ pub fn parse_desktop_file(path: &Path) -> (Option, Option) { let entry = match freedesktop_entry_parser::parse_entry(path) { Ok(ok) => ok, Err(err) => { - log::warn!("failed to parse {:?}: {}", path, err); + log::warn!("failed to parse {}: {}", path.display(), err); return (None, None); } }; @@ -588,11 +588,11 @@ pub fn parse_desktop_file(path: &Path) -> (Option, Option) { entry .section("Desktop Entry") .attr("Name") - .map(|x| x.to_string()), + .map(str::to_string), entry .section("Desktop Entry") .attr("Icon") - .map(|x| x.to_string()), + .map(str::to_string), ) } @@ -606,9 +606,10 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS let remote = file_info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE); let is_dir = matches!(file_info.file_type(), gio::FileType::Directory); - let size_opt = match is_dir { - true => None, - false => Some(file_info.size() as u64), + let size_opt = if is_dir { + None + } else { + Some(file_info.size() as u64) }; let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = if is_dir { @@ -636,7 +637,7 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS }; if let Some(icon_name) = icon_name_opt { ( - mime.clone(), + mime, widget::icon::from_name(&*icon_name) .size(sizes.grid()) .handle(), @@ -667,7 +668,7 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS children_opt = Some(entries.count()); } Err(err) => { - log::warn!("failed to read directory {:?}: {}", path, err); + log::warn!("failed to read directory {}: {}", path.display(), err); } } } @@ -681,7 +682,7 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS size_opt, children_opt, }, - hidden: file_name.starts_with("."), + hidden: file_name.starts_with('.'), location_opt: Some(Location::Path(path)), mime, icon_handle_grid, @@ -711,7 +712,7 @@ pub fn item_from_entry( ) -> Item { let mut display_name = Item::display_name(&name); - let hidden = name.starts_with(".") || hidden_attribute(&metadata); + let hidden = name.starts_with('.') || hidden_attribute(&metadata); let remote = match fs_kind(&metadata) { FsKind::Local => false, @@ -729,7 +730,7 @@ pub fn item_from_entry( display_name = Item::display_name(&info.display_name()); } Err(err) => { - log::warn!("failed to get GIO info for {:?}: {}", path, err); + log::warn!("failed to get GIO info for {}: {}", path.display(), err); } } @@ -740,7 +741,11 @@ pub fn item_from_entry( ) { Ok(info) => info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE), Err(err) => { - log::warn!("failed to get GIO filesystem info for {:?}: {}", path, err); + log::warn!( + "failed to get GIO filesystem info for {}: {}", + path.display(), + err + ); true } } @@ -748,8 +753,8 @@ pub fn item_from_entry( #[cfg(not(feature = "gvfs"))] FsKind::Gvfs => { log::info!( - "gvfs feature not enabled, info may be inaccurate for {:?}", - path + "gvfs feature not enabled, info may be inaccurate for {}", + path.display() ); true } @@ -778,7 +783,7 @@ pub fn item_from_entry( }; if let Some(icon_name) = icon_name_opt { ( - mime.clone(), + mime, widget::icon::from_name(&*icon_name) .size(sizes.grid()) .handle(), @@ -809,7 +814,7 @@ pub fn item_from_entry( children_opt = Some(entries.count()); } Err(err) => { - log::warn!("failed to read directory {:?}: {}", path, err); + log::warn!("failed to read directory {}: {}", path.display(), err); } } } @@ -851,15 +856,15 @@ pub fn item_from_path>(path: P, sizes: IconSizes) -> Result fl!("filesystem"), }; let metadata = fs::metadata(&path) - .map_err(|err| format!("failed to read metadata for {:?}: {}", path, err))?; + .map_err(|err| format!("failed to read metadata for {}: {}", path.display(), err))?; Ok(item_from_entry(path, name, metadata, sizes)) } @@ -903,7 +908,11 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec { } } Err(err) => { - log::warn!("could not enumerate {:?} via gio: {}", tab_path, err); + log::warn!( + "could not enumerate {} via gio: {}", + tab_path.display(), + err + ); } } } @@ -917,7 +926,7 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec { let entry = match entry_res { Ok(ok) => ok, Err(err) => { - log::warn!("failed to read entry in {:?}: {}", tab_path, err); + log::warn!("failed to read entry in {}: {}", tab_path.display(), err); continue; } }; @@ -928,9 +937,9 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec { Ok(ok) => ok, Err(name_os) => { log::warn!( - "failed to parse entry at {:?}: {:?} is not valid UTF-8", - path, - name_os, + "failed to parse entry at {}: {:?} is not valid UTF-8", + path.display(), + name_os ); continue; } @@ -943,7 +952,11 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec { let metadata = match fs::metadata(&path) { Ok(ok) => ok, Err(err) => { - log::warn!("failed to read metadata for entry at {:?}: {}", path, err); + log::warn!( + "failed to read metadata for entry at {}: {}", + path.display(), + err + ); continue; } }; @@ -952,7 +965,7 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec { } } Err(err) => { - log::warn!("failed to read directory {:?}: {}", tab_path, err); + log::warn!("failed to read directory {}: {}", tab_path.display(), err); } } } @@ -961,11 +974,11 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec { (false, true) => Ordering::Greater, _ => LANGUAGE_SORTER.compare(&a.display_name, &b.display_name), }); - items.iter_mut().for_each(|item| { + for item in &mut items { if hidden_files.iter().any(|hidden| &item.name == hidden) { item.hidden = true; } - }); + } items } @@ -986,7 +999,7 @@ pub fn scan_search bool + Sync>( { Ok(ok) => ok, Err(err) => { - log::warn!("failed to parse regex {:?}: {}", pattern, err); + log::warn!("failed to parse regex {pattern:?}: {err}"); return; } }; @@ -1015,7 +1028,11 @@ pub fn scan_search bool + Sync>( let metadata = match entry.metadata() { Ok(ok) => ok, Err(err) => { - log::warn!("failed to read metadata for entry at {:?}: {}", path, err); + log::warn!( + "failed to read metadata for entry at {}: {}", + path.display(), + err + ); return ignore::WalkState::Continue; } }; @@ -1064,7 +1081,7 @@ pub fn scan_trash(sizes: IconSizes) -> Vec { let metadata = match trash::os_limited::metadata(&entry) { Ok(ok) => ok, Err(err) => { - log::warn!("failed to get metadata for trash item {:?}: {}", entry, err); + log::warn!("failed to get metadata for trash item {entry:?}: {err}"); continue; } }; @@ -1118,7 +1135,7 @@ pub fn scan_trash(sizes: IconSizes) -> Vec { } } Err(err) => { - log::warn!("failed to read trash items: {}", err); + log::warn!("failed to read trash items: {err}"); } } items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) { @@ -1171,8 +1188,8 @@ pub fn scan_recents(sizes: IconSizes) -> Vec { Ok(ok) => ok, Err(err) => { log::warn!( - "failed to read metadata for entry at {:?}: {}", - path, + "failed to read metadata for entry at {}: {}", + path.display(), err ); continue; @@ -1187,15 +1204,15 @@ pub fn scan_recents(sizes: IconSizes) -> Vec { } else { last_visit }, - )) + )); } } else { - log::warn!("recent file path not exist: {:?}", path); + log::warn!("recent file path not exist: {}", path.display()); } } } Err(err) => { - log::warn!("Error reading recent files: {:?}", err); + log::warn!("Error reading recent files: {err:?}"); } } @@ -1209,7 +1226,7 @@ pub fn scan_network(uri: &str, sizes: IconSizes) -> Vec { match mounter.network_scan(uri, sizes) { Some(Ok(items)) => return items, Some(Err(err)) => { - log::warn!("failed to scan {:?}: {}", uri, err); + log::warn!("failed to scan {uri:?}: {err}"); } None => {} } @@ -1243,7 +1260,11 @@ pub fn scan_desktop( let mut item = match item_from_path(&path, sizes) { Ok(item) => item, Err(err) => { - log::warn!("failed to get item from mounter item {:?}: {}", path, err); + log::warn!( + "failed to get item from mounter item {}: {}", + path.display(), + err + ); continue; } }; @@ -1301,7 +1322,7 @@ pub fn scan_desktop( overlaps_drag_rect: false, dir_size: DirSize::NotDirectory, cut: false, - }) + }); } items @@ -1373,7 +1394,7 @@ impl std::fmt::Display for Location { Self::Desktop(path, display, ..) => { write!(f, "{} on display {display}", path.display()) } - Self::Network(uri, ..) => write!(f, "{}", uri), + Self::Network(uri, ..) => write!(f, "{uri}"), Self::Path(path) => write!(f, "{}", path.display()), Self::Recents => write!(f, "recents"), Self::Search(path, term, ..) => write!(f, "search {} for {}", path.display(), term), @@ -1384,7 +1405,7 @@ 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()) { + if let Some(mut path) = self.path_opt().cloned() { // Add trailing slash if location is a path path.push(""); self.with_path(path) @@ -1393,7 +1414,7 @@ impl Location { } } - pub fn ancestors(&self) -> Vec<(Location, String)> { + pub fn ancestors(&self) -> Vec<(Self, String)> { let mut ancestors = Vec::new(); if let Some(path) = self.path_opt() { for ancestor in path.ancestors() { @@ -1407,7 +1428,7 @@ impl Location { ancestors } - pub fn path_opt(&self) -> Option<&PathBuf> { + pub const fn path_opt(&self) -> Option<&PathBuf> { match self { Self::Desktop(path, ..) => Some(path), Self::Path(path) => Some(path), @@ -1450,7 +1471,7 @@ impl Location { Some(path) => match item_from_path(path, sizes) { Ok(item) => Some(item), Err(err) => { - log::warn!("failed to get item for {:?}: {}", path, err); + log::warn!("failed to get item for {}: {}", path.display(), err); None } }, @@ -1473,7 +1494,7 @@ impl Location { Self::Search(path, term, ..) => { //TODO: translate let (name, _) = folder_name(path); - format!("Search \"{}\": {}", term, name) + format!("Search \"{term}\": {name}") } Self::Trash => { fl!("trash") @@ -1669,10 +1690,13 @@ impl ItemMetadata { pub fn file_size(&self) -> Option { match self { - Self::Path { metadata, .. } => match metadata.is_dir() { - true => None, - false => Some(metadata.len()), - }, + Self::Path { metadata, .. } => { + if metadata.is_dir() { + None + } else { + Some(metadata.len()) + } + } Self::Trash { metadata, .. } => match metadata.size { TrashItemSize::Bytes(size) => Some(size), TrashItemSize::Entries(_) => None, @@ -1721,14 +1745,14 @@ impl ItemThumbnail { match thumbnail_cacher.as_ref() { Ok(cache) => match cache.get_cached_thumbnail() { CachedThumbnail::Valid((path, size)) => { - return ItemThumbnail::Image( + return Self::Image( widget::image::Handle::from_path(path), size.map(|s| (s.pixel_size(), s.pixel_size())), ); } CachedThumbnail::Failed => { if mime.type_() != mime::IMAGE { - return ItemThumbnail::NotImage; + return Self::NotImage; } } CachedThumbnail::RequiresUpdate(size) => { @@ -1736,7 +1760,11 @@ impl ItemThumbnail { } }, Err(err) => { - log::warn!("failed to create ThumbnailCache for {:?}: {}", path, err); + log::warn!( + "failed to create ThumbnailCache for {}: {}", + path.display(), + err + ); } } @@ -1746,9 +1774,9 @@ impl ItemThumbnail { true } else { log::warn!( - "skipping internal {} thumbnailer for {:?}: file size {} is larger than {}", + "skipping internal {} thumbnailer for {}: file size {} is larger than {}", thumbnailer, - path, + path.display(), format_size(size), format_size(max_size) ); @@ -1771,23 +1799,25 @@ impl ItemThumbnail { match image::DynamicImage::from_decoder(decoder) { Ok(img) => Some(img), Err(err) => { - log::warn!("failed to decode jxl {:?}: {}", path, err); + log::warn!("failed to decode jxl {}: {}", path.display(), err); None } } } Err(err) => { - log::warn!("failed to create jxl decoder {:?}: {}", path, err); + log::warn!("failed to create jxl decoder {}: {}", path.display(), err); None } }, Err(err) => { - log::warn!("failed to open path {:?}: {}", path, err); + log::warn!("failed to open path {}: {}", path.display(), err); None } }, _ => { - match image::ImageReader::open(path).and_then(|img| img.with_guessed_format()) { + match image::ImageReader::open(path) + .and_then(image::ImageReader::with_guessed_format) + { Ok(mut reader) => { let mut limits = image::Limits::default(); let max_ram = max_mem * 1000 * 1000 / jobs as u64; @@ -1796,13 +1826,13 @@ impl ItemThumbnail { match reader.decode() { Ok(reader) => Some(reader), Err(err) => { - log::warn!("failed to decode {:?}: {}", path, err); + log::warn!("failed to decode {}: {}", path.display(), err); None } } } Err(err) => { - log::warn!("failed to read {:?}: {}", path, err); + log::warn!("failed to read {}: {}", path.display(), err); None } } @@ -1813,13 +1843,10 @@ impl ItemThumbnail { if let Ok(cacher) = thumbnail_cacher.as_ref() { match cacher.update_with_image(dyn_img) { Ok(path) => { - return ItemThumbnail::Image( - widget::image::Handle::from_path(path), - None, - ); + return Self::Image(widget::image::Handle::from_path(path), None); } Err(err) => { - log::warn!("cacher failed to decode {:?}: {}", path, err); + log::warn!("cacher failed to decode {}: {}", path.display(), err); } } } else { @@ -1827,7 +1854,7 @@ impl ItemThumbnail { let thumbnail = dyn_img .thumbnail(thumbnail_size, thumbnail_size) .into_rgba8(); - return ItemThumbnail::Image( + return Self::Image( widget::image::Handle::from_rgba( thumbnail.width(), thumbnail.height(), @@ -1840,13 +1867,16 @@ impl ItemThumbnail { } // Try external thumbnailers. - let thumbnail_dir = thumbnail_cacher.as_ref().ok().map(|c| c.thumbnail_dir()); + let thumbnail_dir = thumbnail_cacher + .as_ref() + .ok() + .map(ThumbnailCacher::thumbnail_dir); if let Some((item_thumbnail, temp_file)) = Self::generate_thumbnail_external(path, &mime, thumbnail_size, thumbnail_dir) { if let Ok(cache) = thumbnail_cacher { if let Err(err) = cache.update_with_temp_file(temp_file) { - log::warn!("failed to update cache for {:?}: {}", path, err); + log::warn!("failed to update cache for {}: {}", path.display(), err); } } return item_thumbnail; @@ -1865,20 +1895,20 @@ impl ItemThumbnail { match fs::read(path) { Ok(data) => { //TODO: validate SVG data - return ItemThumbnail::Svg(widget::svg::Handle::from_memory(data)); + return Self::Svg(widget::svg::Handle::from_memory(data)); } Err(err) => { - log::warn!("failed to read {:?}: {}", path, err); + log::warn!("failed to read {}: {}", path.display(), err); } } } else if mime.type_() == mime::TEXT && check_size("text", 8 * 1000 * 1000) { - /*TODO: fix performance issues, widget::text_editr::Content::with_text forces all text to shape, which blocks rendering + /*TODO: fix performance issues, widget::text_editor::Content::with_text forces all text to shape, which blocks rendering match fs::read_to_string(&path) { Ok(data) => { return ItemThumbnail::Text(widget::text_editor::Content::with_text(&data)); } Err(err) => { - log::warn!("failed to read {:?}: {}", path, err); + log::warn!("failed to read {}: {}", path.display(), err); } } */ @@ -1891,15 +1921,15 @@ impl ItemThumbnail { if tried_supported_file { if let Err(err) = cacher.create_fail_marker() { log::warn!( - "failed to create thumbnail fail marker for {:?}: {}", - path, + "failed to create thumbnail fail marker for {}: {}", + path.display(), err ); } } } - ItemThumbnail::NotImage + Self::NotImage } fn generate_thumbnail_external( @@ -1907,7 +1937,7 @@ impl ItemThumbnail { mime: &mime::Mime, thumbnail_size: u32, thumbnail_dir: Option<&Path>, - ) -> Option<(ItemThumbnail, NamedTempFile)> { + ) -> Option<(Self, NamedTempFile)> { // Try external thumbnailers for thumbnailer in thumbnailer(mime) { let is_evince = thumbnailer.exec.starts_with("evince-thumbnailer "); @@ -1931,8 +1961,8 @@ impl ItemThumbnail { Ok(ok) => ok, Err(err) => { log::warn!( - "failed to create temporary file for thumbnail of {:?}: {}", - path, + "failed to create temporary file for thumbnail of {}: {}", + path.display(), err ); continue; @@ -1946,12 +1976,13 @@ impl ItemThumbnail { Ok(status) => { if status.success() { match image::ImageReader::open(file.path()) - .and_then(|img| img.with_guessed_format()) + .and_then(image::ImageReader::with_guessed_format) { - Ok(reader) => match reader.decode().map(|image| image.into_rgba8()) { + Ok(reader) => match reader.decode().map(image::DynamicImage::into_rgb8) + { Ok(image) => { return Some(( - ItemThumbnail::Image( + Self::Image( widget::image::Handle::from_rgba( image.width(), image.height(), @@ -1963,19 +1994,28 @@ impl ItemThumbnail { )); } Err(err) => { - log::warn!("failed to decode {:?}: {}", path, err); + log::warn!("failed to decode {}: {}", path.display(), err); } }, Err(err) => { - log::warn!("failed to read {:?}: {}", path, err); + log::warn!("failed to read {}: {}", path.display(), err); } } } else { - log::warn!("failed to run {:?} for {:?}: {}", thumbnailer, path, status); + log::warn!( + "failed to run {:?} for {}: {}", + thumbnailer, + path.display(), + status + ); } } Err(err) => { - log::warn!("failed to run {:?} for {:?}: {}", thumbnailer, path, err); + log::warn!( + "failed to run {thumbnailer:?} for {}: {}", + path.display(), + err + ); } } } @@ -2010,7 +2050,7 @@ pub struct Item { impl Item { fn display_name(name: &str) -> String { // In order to wrap at periods and underscores, add a zero width space after each one - name.replace(".", ".\u{200B}").replace("_", "_\u{200B}") + name.replace('.', ".\u{200B}").replace('_', "_\u{200B}") } pub fn path_opt(&self) -> Option<&PathBuf> { @@ -2267,15 +2307,14 @@ impl Item { if let Some(path) = self.path_opt() { if let Ok(img) = image::image_dimensions(path) { let (width, height) = img; - details = details.push(widget::text::body(format!("{}x{}", width, height))); + details = details.push(widget::text::body(format!("{width}x{height}"))); } } column = column.push(details); if let Some(path) = self.path_opt() { column = column.push( - widget::button::standard(fl!("open")) - .on_press(Message::Open(Some(path.to_path_buf()))), + widget::button::standard(fl!("open")).on_press(Message::Open(Some(path.clone()))), ); } @@ -2301,34 +2340,32 @@ impl Item { //TODO: translate! //TODO: correct display of folder size? - match &self.metadata { - ItemMetadata::Path { - metadata, - children_opt, - } => { - if metadata.is_dir() { - if let Some(children) = children_opt { - column = column.push(widget::text::body(format!("Items: {}", children))); - } - } else { - column = column.push(widget::text::body(format!( - "Size: {}", - format_size(metadata.len()) - ))); + if let ItemMetadata::Path { + metadata, + children_opt, + } = &self.metadata + { + if metadata.is_dir() { + if let Some(children) = children_opt { + column = column.push(widget::text::body(format!("Items: {children}"))); } - if let Ok(time) = metadata.modified() { - let date_time_formatter = date_time_formatter(military_time); - let time_formatter = time_formatter(military_time); + } else { + column = column.push(widget::text::body(format!( + "Size: {}", + format_size(metadata.len()) + ))); + } + if let Ok(time) = metadata.modified() { + let date_time_formatter = date_time_formatter(military_time); + let time_formatter = time_formatter(military_time); - column = column.push(widget::text::body(format!( - "Last modified: {}", - format_time(time, &date_time_formatter, &time_formatter) - ))); - } - } - _ => { - //TODO: other metadata + column = column.push(widget::text::body(format!( + "Last modified: {}", + format_time(time, &date_time_formatter, &time_formatter) + ))); } + } else { + //TODO: other metadata } row = row.push(column); @@ -2352,10 +2389,10 @@ pub enum HeadingOptions { impl fmt::Display for HeadingOptions { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - HeadingOptions::Name => write!(f, "{}", fl!("name")), - HeadingOptions::Modified => write!(f, "{}", fl!("modified")), - HeadingOptions::Size => write!(f, "{}", fl!("size")), - HeadingOptions::TrashedOn => write!(f, "{}", fl!("trashed-on")), + Self::Name => write!(f, "{}", fl!("name")), + Self::Modified => write!(f, "{}", fl!("modified")), + Self::Size => write!(f, "{}", fl!("size")), + Self::TrashedOn => write!(f, "{}", fl!("trashed-on")), } } } @@ -2363,10 +2400,10 @@ impl fmt::Display for HeadingOptions { impl HeadingOptions { pub fn names() -> Vec { vec![ - HeadingOptions::Name.to_string(), - HeadingOptions::Modified.to_string(), - HeadingOptions::Size.to_string(), - HeadingOptions::TrashedOn.to_string(), + Self::Name.to_string(), + Self::Modified.to_string(), + Self::Size.to_string(), + Self::TrashedOn.to_string(), ] } } @@ -2382,8 +2419,8 @@ impl Mode { /// Whether multiple files can be selected in this mode pub fn multiple(&self) -> bool { match self { - Mode::App | Mode::Desktop => true, - Mode::Dialog(dialog) => dialog.multiple(), + Self::App | Self::Desktop => true, + Self::Dialog(dialog) => dialog.multiple(), } } } @@ -2505,7 +2542,7 @@ fn parse_hidden_file(path: &PathBuf) -> Vec { BufReader::new(file) .lines() .map_while(Result::ok) - .flat_map(|line| { + .filter_map(|line| { let line = line.trim(); (!line.is_empty()).then_some(line.to_owned()) }) @@ -2525,7 +2562,7 @@ impl Tab { let (sort_name, sort_direction) = sorting_options .and_then(|opts| opts.get(&location_str)) .or_else(|| SORT_OPTION_FALLBACK.get(&location_str)) - .cloned() + .copied() .unwrap_or((HeadingOptions::Name, true)); let location = location.normalize(); let location_ancestors = location.ancestors(); @@ -2575,17 +2612,17 @@ impl Tab { self.location_title.clone() } - pub fn items_opt(&self) -> Option<&Vec> { + pub const fn items_opt(&self) -> Option<&Vec> { self.items_opt.as_ref() } - pub fn items_opt_mut(&mut self) -> Option<&mut Vec> { + pub const fn items_opt_mut(&mut self) -> Option<&mut Vec> { self.items_opt.as_mut() } pub fn set_items(&mut self, mut items: Vec) { let selected = self.selected_locations(); - for item in items.iter_mut() { + for item in &mut items { item.selected = false; if let Some(location) = &item.location_opt { if selected.contains(location) { @@ -2623,7 +2660,7 @@ impl Tab { pub fn selected_locations(&self) -> Vec { let mut locations = Vec::new(); if let Some(ref items) = self.items_opt { - for item in items.iter() { + for item in items { if item.selected { if let Some(location) = &item.location_opt { locations.push(location.clone()); @@ -2741,11 +2778,7 @@ impl Tab { if let Some(ref mut items) = self.items_opt { for item in items.iter_mut() { let was_overlapped = item.overlaps_drag_rect; - item.overlaps_drag_rect = item - .rect_opt - .get() - .map(|r| r.intersects(&rect)) - .unwrap_or(false); + item.overlaps_drag_rect = item.rect_opt.get().is_some_and(|r| r.intersects(&rect)); item.selected = if mod_ctrl || mod_shift { if was_overlapped == item.overlaps_drag_rect { @@ -2816,7 +2849,7 @@ impl Tab { fn select_first_pos_opt(&self) -> Option<(usize, usize)> { let items = self.items_opt.as_ref()?; let mut first = None; - for item in items.iter() { + for item in items { if !item.selected { continue; } @@ -2841,7 +2874,7 @@ impl Tab { fn select_last_pos_opt(&self) -> Option<(usize, usize)> { let items = self.items_opt.as_ref()?; let mut last = None; - for item in items.iter() { + for item in items { if !item.selected { continue; } @@ -2927,12 +2960,12 @@ impl Tab { if item.metadata.is_dir() { cd = Some(location.clone()); } else if let Some(path) = location.path_opt() { - paths_to_open.push(path.to_path_buf()); + paths_to_open.push(path.clone()); } else { - log::warn!("no path for item {:?}", item); + log::warn!("no path for item {item:?}"); } } else { - log::warn!("no location for item {:?}", item); + log::warn!("no location for item {item:?}"); } } } @@ -2973,15 +3006,15 @@ impl Tab { if clicked_item.metadata.is_dir() { cd = Some(location.clone()); } else if let Some(path) = location.path_opt() { - commands.push(Command::OpenFile(vec![path.to_path_buf()])); + commands.push(Command::OpenFile(vec![path.clone()])); } else { - log::warn!("no path for item {:?}", clicked_item); + log::warn!("no path for item {clicked_item:?}"); } } else { - log::warn!("no location for item {:?}", clicked_item); + log::warn!("no location for item {clicked_item:?}"); } } else { - log::warn!("no item for click index {:?}", click_i_opt); + log::warn!("no item for click index {click_i_opt:?}"); } } Message::Click(click_i_opt) => { @@ -3023,7 +3056,7 @@ impl Tab { let len = self .items_opt .as_deref() - .map(|items| items.len()) + .map(<[Item]>::len) .unwrap_or_default(); (0..len).collect() }); @@ -3160,7 +3193,7 @@ impl Tab { self.location .path_opt() .and_then(|path| path.ancestors().nth(ancestor_index)) - .map(|path| path.to_path_buf()) + .map(Path::to_path_buf) }; match action { LocationMenuAction::OpenInNewTab(ancestor_index) => { @@ -3183,7 +3216,11 @@ impl Tab { ))); } Err(err) => { - log::warn!("failed to get item from path {:?}: {}", path, err); + log::warn!( + "failed to get item from path {}: {}", + path.display(), + err + ); } } } @@ -3307,7 +3344,7 @@ impl Tab { } Message::GalleryToggle => { if let Some(indices) = self.column_sort() { - for (_, item) in indices.iter() { + for (_, item) in &indices { if item.selected && item.can_gallery() { self.gallery = !self.gallery; break; @@ -3391,7 +3428,7 @@ impl Tab { if !row.checked_sub(1).is_some_and(|row| { let mut col = 0; if let Some(ref items) = self.items_opt { - for item in items.iter() { + for item in items { match item.pos_opt.get() { Some((item_row, item_col)) if item_row == row => { col = col.max(item_col); @@ -3543,7 +3580,7 @@ impl Tab { //TODO: allow opening multiple tabs? cd = Some(location.clone()); } else if let Some(path) = location.path_opt() { - open_files.push(path.to_path_buf()); + open_files.push(path.clone()); } } else { //TODO: open properties? @@ -3561,7 +3598,7 @@ impl Tab { //TODO: support keeping selected locations without paths for location in self.selected_locations() { if let Some(path) = location.path_opt() { - selected_paths.push(path.to_path_buf()); + selected_paths.push(path.clone()); } } let location = self.location.clone(); @@ -3605,15 +3642,15 @@ impl Tab { if let Some(path) = clicked_item.path_opt() { if clicked_item.metadata.is_dir() { //cd = Some(Location::Path(path.clone())); - commands.push(Command::OpenInNewTab(path.clone())) + commands.push(Command::OpenInNewTab(path.clone())); } else { commands.push(Command::OpenFile(vec![path.clone()])); } } else { - log::warn!("no path for item {:?}", clicked_item); + log::warn!("no path for item {clicked_item:?}"); } } else { - log::warn!("no item for click index {:?}", click_i); + log::warn!("no item for click index {click_i:?}"); } } } @@ -3836,7 +3873,7 @@ impl Tab { | Location::Path(to) | Location::Network(_, _, Some(to)) => { if let Ok(entries) = fs::read_dir(&to) { - for i in entries.into_iter().filter_map(|e| e.ok()) { + for i in entries.into_iter().filter_map(Result::ok) { let i = i.path(); from.paths.retain(|p| &i != p); if from.paths.is_empty() { @@ -3845,15 +3882,15 @@ impl Tab { } } } - commands.push(Command::DropFiles(to, from)) + commands.push(Command::DropFiles(to, from)); } Location::Trash if matches!(from.kind, ClipboardKind::Cut { .. }) => { - commands.push(Command::Delete(from.paths)) + commands.push(Command::Delete(from.paths)); } _ => { log::warn!("{:?} to {:?} is not supported.", from.kind, to); } - }; + } } Message::Drop(None) => { self.dnd_hovered = None; @@ -3944,7 +3981,7 @@ impl Tab { if let Some(path) = location.path_opt() { if !path.is_dir() { if let Some(parent) = path.parent() { - selected_paths = Some(vec![path.to_path_buf()]); + selected_paths = Some(vec![path.clone()]); location = location.with_path(parent.to_path_buf()); } } @@ -3952,10 +3989,8 @@ impl Tab { if location != self.location || selected_paths.is_some() { if location.path_opt().is_none_or(|path| path.is_dir()) { if selected_paths.is_none() { - selected_paths = self - .location - .path_opt() - .map(|path| vec![path.to_path_buf()]); + selected_paths = + self.location.path_opt().map(|path| vec![path.clone()]); } self.change_location(&location, history_i_opt); commands.push(Command::ChangeLocation( @@ -3964,7 +3999,7 @@ impl Tab { selected_paths, )); } else { - log::warn!("tried to cd to {:?} which is not a directory", location); + log::warn!("tried to cd to {location:?} which is not a directory"); } } } @@ -3983,7 +4018,7 @@ impl Tab { commands } - pub(crate) fn sort_options(&self) -> (HeadingOptions, bool, bool) { + pub(crate) const fn sort_options(&self) -> (HeadingOptions, bool, bool) { match self.location { Location::Search(..) => (HeadingOptions::Modified, false, false), _ => ( @@ -4040,7 +4075,7 @@ impl Tab { (false, true) => Ordering::Greater, _ => check_reverse(a_size.cmp(&b_size), sort_direction), } - }) + }); } HeadingOptions::Name => items.sort_by(|a, b| { if folders_first { @@ -4116,7 +4151,7 @@ impl Tab { data.kind = ClipboardKind::Cut { is_dnd: true }; Message::Drop(Some((location1.clone(), data))) } else { - log::warn!("unsupported action: {:?}", action); + log::warn!("unsupported action: {action:?}"); Message::Drop(None) } } else { @@ -4207,7 +4242,7 @@ impl Tab { ) .center(Length::Fill) .into(), - ) + ); } } } @@ -4331,7 +4366,7 @@ impl Tab { prev_button = prev_button.on_press(Message::GoPrevious); } row = row.push(prev_button); - w += 16.0 + 2.0 * space_xxs as f32; + w += f32::from(space_xxs).mul_add(2.0, 16.0); let mut next_button = widget::button::custom(widget::icon::from_name("go-next-symbolic").size(16)) @@ -4341,10 +4376,10 @@ impl Tab { next_button = next_button.on_press(Message::GoNext); } row = row.push(next_button); - w += 16.0 + 2.0 * space_xxs as f32; + w += f32::from(space_xxs).mul_add(2.0, 16.0); row = row.push(widget::Space::with_width(Length::Fixed(space_s.into()))); - w += space_s as f32; + w += f32::from(space_s); //TODO: allow resizing? let name_width = 300.0; @@ -4408,7 +4443,7 @@ impl Tab { if let Some(edit_location) = &self.edit_location { if let Some(location) = edit_location.resolve() { //TODO: allow editing other locations - if let Some(path) = location.path_opt().map(|x| x.to_path_buf()) { + if let Some(path) = location.path_opt().cloned() { row = row.push( widget::button::custom( widget::icon::from_name("window-close-symbolic").size(16), @@ -4417,7 +4452,6 @@ impl Tab { .padding(space_xxs) .class(theme::Button::Icon), ); - let location = location.clone(); let text_input = widget::text_input("", path.to_string_lossy().to_string()) .id(self.edit_location_id.clone()) .on_input(move |input| { @@ -4477,7 +4511,7 @@ impl Tab { ) .on_middle_press(move |_| Message::OpenInNewTab(path.clone())), ); - w += 16.0 + 2.0 * space_xxs as f32; + w += f32::from(space_xxs).mul_add(2.0, 16.0); } let mut children: Vec> = Vec::new(); @@ -4507,7 +4541,7 @@ impl Tab { }; // Add padding for mouse area - w += 2.0 * space_xxxs as f32; + w += 2.0 * f32::from(space_xxxs); let mut row = widget::row::with_capacity(2) .align_y(Alignment::Center) @@ -4540,14 +4574,14 @@ impl Tab { .on_right_press(move |point_opt| { Message::LocationContextMenuIndex(point_opt, None) }) - .wayland_on_right_press_window_position() + .wayland_on_right_press_window_position(); } else { mouse_area = mouse_area .on_right_press_no_capture() .on_right_press(move |point_opt| { Message::LocationContextMenuIndex(point_opt, Some(index)) }) - .wayland_on_right_press_window_position() + .wayland_on_right_press_window_position(); } let mouse_area = if let Location::Path(_) = &self.location { @@ -4620,7 +4654,7 @@ impl Tab { ) { popover = popover .popup(menu::location_context_menu(index)) - .position(widget::popover::Position::Point(point)) + .position(widget::popover::Position::Point(point)); } popover.into() @@ -4681,7 +4715,7 @@ impl Tab { if let Location::Desktop(_path, _output, desktop_config) = &self.location { icon_sizes.grid = desktop_config.icon_size; grid_spacing = desktop_config.grid_spacing_for(space_xxs); - }; + } let text_height = 3 * 20; // 3 lines of text let item_width = (3 * space_xxs + icon_sizes.grid() + 3 * space_xxs) as usize; @@ -4743,7 +4777,7 @@ impl Tab { let mut page_row = 0; let mut hidden = 0; let mut grid_elements = Vec::new(); - for &(i, item) in items.iter() { + for &(i, item) in &items { if !show_hidden && item.hidden { item.pos_opt.set(None); item.rect_opt.set(None); @@ -4809,7 +4843,7 @@ impl Tab { .width(Length::Fixed(item_width as f32)); for button in buttons { if self.context_menu.is_some() { - column = column.push(button) + column = column.push(button); } else { column = column.push( mouse_area::MouseArea::new(button) @@ -4914,7 +4948,7 @@ impl Tab { children.push( widget::container(Space::with_height(Length::Fixed(spacer_height as f32))) .into(), - ) + ); } } } @@ -5067,8 +5101,8 @@ impl Tab { item.pos_opt.set(Some((count, 0))); let item_rect = Rectangle::new( - Point::new(space_s as f32, y), - Size::new(size.width - (2 * space_s) as f32, row_height as f32), + Point::new(f32::from(space_s), y), + Size::new(size.width - f32::from(2 * space_s), f32::from(row_height)), ); item.rect_opt.set(Some(item_rect)); @@ -5103,9 +5137,9 @@ impl Tab { //TODO: translate if let Some(children) = children_opt { if *children == 1 { - format!("{} item", children) + format!("{children} item") } else { - format!("{} items", children) + format!("{children} items") } } else { String::new() @@ -5118,9 +5152,9 @@ impl Tab { trash::TrashItemSize::Entries(entries) => { //TODO: translate if entries == 1 { - format!("{} item", entries) + format!("{entries} item") } else { - format!("{} items", entries) + format!("{entries} items") } } trash::TrashItemSize::Bytes(bytes) => format_size(bytes), @@ -5128,9 +5162,9 @@ impl Tab { ItemMetadata::SimpleDir { entries } => { //TODO: translate if *entries == 1 { - format!("{} item", entries) + format!("{entries} item") } else { - format!("{} items", entries) + format!("{entries} items") } } ItemMetadata::SimpleFile { size } => format_size(*size), @@ -5142,9 +5176,9 @@ impl Tab { } => match children_opt { Some(child_count) => { if *child_count == 1 { - format!("{} item", child_count) + format!("{child_count} item") } else { - format!("{} items", child_count) + format!("{child_count} items") } } None => format_size(size_opt.unwrap_or_default()), @@ -5160,12 +5194,12 @@ impl Tab { widget::column::with_children(vec![ widget::text::body(item.display_name.clone()).into(), //TODO: translate? - widget::text::caption(format!("{} - {}", modified_text, size_text)) + widget::text::caption(format!("{modified_text} - {size_text}")) .into(), ]) .into(), ]) - .height(Length::Fixed(row_height as f32)) + .height(Length::Fixed(f32::from(row_height))) .align_y(Alignment::Center) .spacing(space_xxs) } else if is_search { @@ -5191,7 +5225,7 @@ impl Tab { .width(Length::Fixed(size_width)) .into(), ]) - .height(Length::Fixed(row_height as f32)) + .height(Length::Fixed(f32::from(row_height))) .align_y(Alignment::Center) .spacing(space_xxs) } else { @@ -5210,7 +5244,7 @@ impl Tab { .width(Length::Fixed(size_width)) .into(), ]) - .height(Length::Fixed(row_height as f32)) + .height(Length::Fixed(f32::from(row_height))) .align_y(Alignment::Center) .spacing(space_xxs) }; @@ -5259,7 +5293,7 @@ impl Tab { if item.selected || !drag_items.is_empty() { let dnd_row = if !item.selected { - Element::from(Space::with_height(Length::Fixed(row_height as f32))) + Element::from(Space::with_height(Length::Fixed(f32::from(row_height)))) } else if condensed { widget::row::with_children(vec![ widget::icon::icon(item.icon_handle_list_condensed.clone()) @@ -5269,11 +5303,8 @@ impl Tab { widget::column::with_children(vec![ widget::text::body(item.display_name.clone()).into(), //TODO: translate? - widget::text::body(format!( - "{} - {}", - modified_text, size_text - )) - .into(), + widget::text::body(format!("{modified_text} - {size_text}")) + .into(), ]) .into(), ]) @@ -5341,12 +5372,12 @@ impl Tab { } else { widget::column() .width(Length::Fill) - .height(Length::Fixed(row_height as f32)) + .height(Length::Fixed(f32::from(row_height))) .into() }; count += 1; - y += row_height as f32; + y += f32::from(row_height); children.push(button_row); } @@ -5361,10 +5392,10 @@ impl Tab { self.item_view_size_opt .set(self.size_opt.get().map(|s| Size { width: s.width, - height: s.height - top_deduct as f32, + height: s.height - f32::from(top_deduct), })); - let spacer_height = size.height - y - top_deduct as f32; + let spacer_height = size.height - y - f32::from(top_deduct); if spacer_height > 0. { children.push( widget::container(Space::with_height(Length::Fixed(spacer_height))).into(), @@ -5445,8 +5476,8 @@ impl Tab { match view { // offset by grid padding so that we grab the top left corner of the item in the drag grid. View::Grid => Vector::new( - -3. * space_xxs as f32 - space_xxxs as f32, - -4. * (space_xxxs as f32), + f32::from(space_xxs).mul_add(-3.0, -f32::from(space_xxxs)), + -4. * f32::from(space_xxxs), ), View::List => Vector::ZERO, }, @@ -5562,7 +5593,7 @@ impl Tab { data.kind = ClipboardKind::Cut { is_dnd: true }; Message::Drop(Some((tab_location.clone(), data))) } else { - log::warn!("unsupported action: {:?}", action); + log::warn!("unsupported action: {action:?}"); Message::Drop(None) } } else { @@ -5595,7 +5626,7 @@ impl Tab { Rectangle::new(point, size) }; - for item in items.iter() { + for item in items { if item.thumbnail_opt.is_some() { // Skip items that already have a mime type and thumbnail continue; @@ -5614,7 +5645,7 @@ impl Tab { } } - let Some(path) = item.path_opt().map(|path| path.to_path_buf()) else { + let Some(path) = item.path_opt().cloned() else { continue; }; @@ -5628,8 +5659,8 @@ impl Tab { if can_thumbnail { let mime = item.mime.clone(); let max_jobs = jobs; - let max_mb = self.thumb_config.max_mem_mb.get() as u64; - let max_size = self.thumb_config.max_size_mb.get() as u64; + let max_mb = u64::from(self.thumb_config.max_mem_mb.get()); + let max_size = u64::from(self.thumb_config.max_size_mb.get()); subscriptions.push(Subscription::run_with_id( ("thumbnail", path.clone()), stream::channel(1, move |mut output| async move { @@ -5648,8 +5679,12 @@ impl Tab { max_jobs, max_size, ); - log::debug!("thumbnailed {:?} in {:?}", path, start.elapsed()); - Message::Thumbnail(path.clone(), thumbnail) + log::debug!( + "thumbnailed {} in {:?}", + path.display(), + start.elapsed() + ); + Message::Thumbnail(path, thumbnail) }) .await .unwrap() @@ -5658,7 +5693,11 @@ impl Tab { match output.send(message).await { Ok(()) => {} Err(err) => { - log::warn!("failed to send thumbnail for {:?}: {}", &path, err); + log::warn!( + "failed to send thumbnail for {}: {}", + path.display(), + err + ); } } @@ -5680,7 +5719,7 @@ impl Tab { .or(self.parent_item_opt.as_ref()) { // Item must have a path - if let Some(path) = item.path_opt().map(|path| path.to_path_buf()) { + if let Some(path) = item.path_opt().cloned() { // Item must be calculating directory size if let DirSize::Calculating(controller) = &item.dir_size { let controller = controller.clone(); @@ -5692,8 +5731,8 @@ impl Tab { match calculate_dir_size(&path, controller).await { Ok(size) => { log::debug!( - "calculated directory size of {:?} in {:?}", - path, + "calculated directory size of {} in {:?}", + path.display(), start.elapsed() ); Message::DirectorySize( @@ -5703,10 +5742,10 @@ impl Tab { } Err(err) => { log::warn!( - "failed to calculate directory size of {:?}: {}", - path, - err - ); + "failed to calculate directory size of {}: {}", + path.display(), + err + ); Message::DirectorySize( path.clone(), DirSize::Error(err.to_string()), @@ -5719,8 +5758,8 @@ impl Tab { Ok(()) => {} Err(err) => { log::warn!( - "failed to send directory size for {:?}: {}", - &path, + "failed to send directory size for {}: {}", + path.display(), err ); } @@ -5789,7 +5828,9 @@ impl Tab { metadata, )) { Ok(()) => { - if !ready.swap(true, atomic::Ordering::SeqCst) { + if ready.swap(true, atomic::Ordering::SeqCst) { + true + } else { // Wake up update method futures::executor::block_on(async { output @@ -5799,8 +5840,6 @@ impl Tab { .await }) .is_ok() - } else { - true } } Err(_) => false, @@ -5808,9 +5847,9 @@ impl Tab { }, ); log::info!( - "searched for {:?} in {:?} in {:?}", + "searched for {:?} in {} in {:?}", term, - path, + path.display(), start.elapsed(), ); }) @@ -5830,7 +5869,7 @@ impl Tab { .edit_location .as_ref() .and_then(|x| x.location.path_opt()) - .map(|x| x.to_path_buf()) + .cloned() { subscriptions.push(Subscription::run_with_id( ("tab_complete", path.to_string_lossy().to_string()), @@ -5841,11 +5880,19 @@ impl Tab { let start = Instant::now(); match tab_complete(&path) { Ok(completions) => { - log::info!("tab completed {:?} in {:?}", path, start.elapsed()); + log::info!( + "tab completed {} in {:?}", + path.display(), + start.elapsed() + ); Message::TabComplete(path.clone(), completions) } Err(err) => { - log::warn!("failed to tab complete {:?}: {}", path, err); + log::warn!( + "failed to tab complete {}: {}", + path.display(), + err + ); Message::TabComplete(path.clone(), Vec::new()) } } @@ -5857,7 +5904,11 @@ impl Tab { match output.send(message).await { Ok(()) => {} Err(err) => { - log::warn!("failed to send tab completion for {:?}: {}", path, err); + log::warn!( + "failed to send tab completion for {}: {}", + path.display(), + err + ); } } @@ -5869,7 +5920,7 @@ impl Tab { Subscription::batch(subscriptions) } - fn format_time<'a>(&'a self, time: SystemTime) -> FormatTime<'a> { + const fn format_time(&self, time: SystemTime) -> FormatTime<'_> { format_time(time, &self.date_time_formatter, &self.time_formatter) } } @@ -5929,7 +5980,7 @@ impl Widget for RcElementWrapper { self.0 .borrow() .as_widget() - .draw(tree, renderer, theme, style, layout, cursor, viewport) + .draw(tree, renderer, theme, style, layout, cursor, viewport); } fn tag(&self) -> tree::Tag { @@ -5945,7 +5996,7 @@ impl Widget for RcElementWrapper { } fn diff(&mut self, tree: &mut tree::Tree) { - self.0.borrow_mut().as_widget_mut().diff(tree) + self.0.borrow_mut().as_widget_mut().diff(tree); } fn operate( @@ -5958,7 +6009,7 @@ impl Widget for RcElementWrapper { self.0 .borrow() .as_widget() - .operate(state, layout, renderer, operation) + .operate(state, layout, renderer, operation); } fn on_event( @@ -6007,7 +6058,7 @@ impl Widget for RcElementWrapper { } fn set_id(&mut self, _id: Id) { - self.0.borrow_mut().as_widget_mut().set_id(_id) + self.0.borrow_mut().as_widget_mut().set_id(_id); } fn drag_destinations( @@ -6020,7 +6071,7 @@ impl Widget for RcElementWrapper { self.0 .borrow() .as_widget() - .drag_destinations(_state, _layout, renderer, _dnd_rectangles) + .drag_destinations(_state, _layout, renderer, _dnd_rectangles); } } @@ -6445,7 +6496,7 @@ mod tests { debug!("Shuffled numbers for paths: {base_nums:?}"); let paths: Vec<_> = base_nums .iter() - .map(|&base| path.join(std::iter::repeat(base).take(255).collect::())) + .map(|&base| path.join(std::iter::repeat_n(base, 255).collect::())) .collect(); for (file, &base) in paths.iter().zip(base_nums.iter()) { @@ -6477,32 +6528,29 @@ mod tests { let mode = (user << MODE_SHIFT_USER) | (group << MODE_SHIFT_GROUP) | (other << MODE_SHIFT_OTHER); - assert_eq!( - format!("{:03o}", mode), - format!("{:o}{:o}{:o}", user, group, other), - ); + assert_eq!(format!("{mode:03o}"), format!("{user:o}{group:o}{other:o}"),); assert_eq!(get_mode_part(mode, MODE_SHIFT_USER), user); assert_eq!(get_mode_part(mode, MODE_SHIFT_GROUP), group); assert_eq!(get_mode_part(mode, MODE_SHIFT_OTHER), other); let mode_no_user = (group << MODE_SHIFT_GROUP) | (other << MODE_SHIFT_OTHER); assert_eq!( - format!("{:03o}", mode_no_user), - format!("0{:o}{:o}", group, other) + format!("{mode_no_user:03o}"), + format!("0{group:o}{other:o}") ); assert_eq!(set_mode_part(mode_no_user, MODE_SHIFT_USER, user), mode); let mode_no_group = (user << MODE_SHIFT_USER) | (other << MODE_SHIFT_OTHER); assert_eq!( - format!("{:03o}", mode_no_group), - format!("{:o}0{:o}", user, other) + format!("{mode_no_group:03o}"), + format!("{user:o}0{other:o}") ); assert_eq!(set_mode_part(mode_no_group, MODE_SHIFT_GROUP, group), mode); let mode_no_other = (user << MODE_SHIFT_USER) | (group << MODE_SHIFT_GROUP); assert_eq!( - format!("{:03o}", mode_no_other), - format!("{:o}{:o}0", user, group) + format!("{mode_no_other:03o}"), + format!("{user:o}{group:o}0") ); assert_eq!(set_mode_part(mode_no_other, MODE_SHIFT_OTHER, other), mode); } diff --git a/src/thumbnail_cacher.rs b/src/thumbnail_cacher.rs index 28da5c6..447b848 100644 --- a/src/thumbnail_cacher.rs +++ b/src/thumbnail_cacher.rs @@ -14,7 +14,7 @@ use tempfile::NamedTempFile; use url::Url; /// Implements thumbnail caching based on the freedesktop.org Thumbnail Managing Standard. -/// https://specifications.freedesktop.org/thumbnail-spec/latest/ +/// / pub struct ThumbnailCacher { file_path: PathBuf, file_uri: String, @@ -27,18 +27,22 @@ pub struct ThumbnailCacher { impl ThumbnailCacher { pub fn new(file_path: &Path, thumbnail_size: ThumbnailSize) -> Result { let file_uri = thumbnail_uri(file_path) - .map_err(|err| format!("failed to create URI for {file_path:?}: {err}"))?; + .map_err(|err| format!("failed to create URI for {}: {}", file_path.display(), err))?; let cache_base_dir = THUMBNAIL_CACHE_BASE_DIR .as_ref() .ok_or("failed to get thumbnail cache directory".to_string())?; let thumbnail_filename = thumbnail_cache_filename(&file_uri); let thumbnail_dir = cache_base_dir.join(thumbnail_size.subdirectory_name()); if !thumbnail_dir.is_dir() { - log::warn!("{:?} is not a directory, creating one now", &thumbnail_dir); - fs::create_dir_all(&thumbnail_dir).unwrap_or(log::error!( - "{:?} failed to create directory, this error can be expected on first run", - &thumbnail_dir - )); + log::warn!( + "{} is not a directory, creating one now", + thumbnail_dir.display() + ); + let _: () = log::error!( + "{} failed to create directory, this error can be expected on first run", + thumbnail_dir.display() + ); + fs::create_dir_all(&thumbnail_dir).unwrap_or(()); } let thumbnail_path = thumbnail_dir.join(&thumbnail_filename); let thumbnail_fail_marker_path = cache_base_dir @@ -64,7 +68,7 @@ impl ThumbnailCacher { std::fs::metadata(&self.file_path), ) { if metadata.is_file() && self.file_path.starts_with(cache_base_dir) { - return CachedThumbnail::Valid((self.file_path.to_path_buf(), None)); + return CachedThumbnail::Valid((self.file_path.clone(), None)); } } @@ -204,7 +208,11 @@ impl ThumbnailCacher { let reader = match decoder.read_info() { Ok(reader) => reader, Err(err) => { - log::warn!("failed to decode {thumbnail_path:?} as PNG: {err}"); + log::warn!( + "failed to decode {} as PNG: {}", + thumbnail_path.display(), + err + ); return false; } }; @@ -227,7 +235,11 @@ impl ThumbnailCacher { let metadata = match std::fs::metadata(&self.file_path) { Ok(m) => m, Err(err) => { - log::warn!("failed to get metatdata of {:?}: {}", self.file_path, err); + log::warn!( + "failed to get metatdata of {}: {}", + self.file_path.display(), + err + ); return false; } }; @@ -242,8 +254,8 @@ impl ThumbnailCacher { Ok(m) => m, Err(err) => { log::warn!( - "failed to get modified from metatdata of {:?}, {}", - self.file_path, + "failed to get modified from metatdata of {}, {}", + self.file_path.display(), err ); return false; @@ -279,16 +291,17 @@ impl ThumbnailCacher { fn thumbnail_uri(path: &Path) -> io::Result { let absolute_path = fs::canonicalize(path)?; - let url = Url::from_file_path(&absolute_path).map_err(|_| { + let url = Url::from_file_path(&absolute_path).map_err(|()| { io::Error::other(format!( - "failed to create URI for thumbnail_file: {absolute_path:?}" + "failed to create URI for thumbnail_file: {}", + absolute_path.display() )) })?; // Technically square brackets don't need to be percent encoded, // and they aren't by the url crate, but the thumbnailer used by // Gnome Files does. In order to share thumbnails and not get duplicates // we should do the same. - let url = url.to_string().replace("[", "%5B").replace("]", "%5D"); + let url = url.to_string().replace('[', "%5B").replace(']', "%5D"); Ok(url) } @@ -319,11 +332,11 @@ impl ThumbnailSize { } } - pub fn pixel_size(self) -> u32 { + pub const fn pixel_size(self) -> u32 { self as u32 } - pub fn subdirectory_name(self) -> &'static str { + pub const fn subdirectory_name(self) -> &'static str { match self { Self::Normal => "normal", Self::Large => "large", diff --git a/src/thumbnailer.rs b/src/thumbnailer.rs index 91b73a5..3627fe5 100644 --- a/src/thumbnailer.rs +++ b/src/thumbnailer.rs @@ -36,7 +36,7 @@ impl Thumbnailer { command.arg(output); } "%s" => { - command.arg(format!("{}", thumbnail_size)); + command.arg(format!("{thumbnail_size}")); } _ => { log::warn!( @@ -89,20 +89,24 @@ impl ThumbnailerCache { let mut thumbnailer_paths = Vec::new(); for dir in search_dirs { - log::trace!("looking for thumbnailers in {:?}", dir); + log::trace!("looking for thumbnailers in {}", dir.display()); match fs::read_dir(&dir) { Ok(entries) => { for entry_res in entries { match entry_res { Ok(entry) => thumbnailer_paths.push(entry.path()), Err(err) => { - log::warn!("failed to read entry in directory {:?}: {}", dir, err); + log::warn!( + "failed to read entry in directory {}: {}", + dir.display(), + err + ); } } } } Err(err) => { - log::warn!("failed to read directory {:?}: {}", dir, err); + log::warn!("failed to read directory {}: {}", dir.display(), err); } } } @@ -112,7 +116,7 @@ impl ThumbnailerCache { let entry = match freedesktop_entry_parser::parse_entry(&path) { Ok(ok) => ok, Err(err) => { - log::warn!("failed to parse {:?}: {}", path, err); + log::warn!("failed to parse {}: {}", path.display(), err); continue; } }; @@ -120,20 +124,23 @@ impl ThumbnailerCache { //TODO: use TryExec? let section = entry.section("Thumbnailer Entry"); let Some(exec) = section.attr("Exec") else { - log::warn!("missing Exec attribute for thumbnailer {:?}", path); + log::warn!("missing Exec attribute for thumbnailer {}", path.display()); continue; }; let Some(mime_types) = section.attr("MimeType") else { - log::warn!("missing MimeType attribute for thumbnailer {:?}", path); + log::warn!( + "missing MimeType attribute for thumbnailer {}", + path.display() + ); continue; }; for mime_type in mime_types.split_terminator(';') { if let Ok(mime) = mime_type.parse::() { - log::trace!("thumbnailer {}={:?}", mime, path); + log::trace!("thumbnailer {}={}", mime, path.display()); let apps = self .cache - .entry(mime.clone()) + .entry(mime) .or_insert_with(|| Vec::with_capacity(1)); apps.push(Thumbnailer { exec: exec.to_string(), @@ -143,11 +150,11 @@ impl ThumbnailerCache { } let elapsed = start.elapsed(); - log::info!("loaded thumbnailer cache in {:?}", elapsed); + log::info!("loaded thumbnailer cache in {elapsed:?}"); } pub fn get(&self, key: &Mime) -> Vec { - self.cache.get(key).map_or_else(Vec::new, |x| x.clone()) + self.cache.get(key).map_or_else(Vec::new, Vec::clone) } } From bd1fa1f0a98e2fb09458fb0efbbb8d3c95fd51da Mon Sep 17 00:00:00 2001 From: Cheong Lau <234708519+Cheong-Lau@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:10:40 +1000 Subject: [PATCH 2/2] perf: general minor performance optimisations Notably there is some code cleanup with the zooming functionality, I've created a new module to reduce code duplication. --- src/app.rs | 836 +++++++++++++++++-------------------- src/archive.rs | 8 +- src/clipboard.rs | 2 +- src/config.rs | 14 +- src/dialog.rs | 188 +++------ src/lib.rs | 1 + src/menu.rs | 17 +- src/mime_app.rs | 38 +- src/mime_icon.rs | 27 +- src/mounter/gvfs.rs | 137 +++--- src/operation/mod.rs | 15 +- src/operation/recursive.rs | 7 +- src/tab.rs | 697 ++++++++++++++----------------- src/thumbnail_cacher.rs | 13 +- src/thumbnailer.rs | 28 +- src/zoom.rs | 52 +++ 16 files changed, 971 insertions(+), 1109 deletions(-) create mode 100644 src/zoom.rs diff --git a/src/app.rs b/src/app.rs index 07f8530..7ec3ce3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -56,7 +56,6 @@ use std::{ env, fmt, fs, future::Future, io, - num::NonZeroU16, path::{Path, PathBuf}, pin::Pin, process, @@ -92,7 +91,11 @@ use crate::{ self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK, Tab, }, }; -use crate::{config::State, dialog::DialogSettings}; +use crate::{ + config::State, + dialog::DialogSettings, + zoom::{zoom_in_view, zoom_out_view, zoom_to_default}, +}; static PERMANENT_DELETE_BUTTON_ID: LazyLock = LazyLock::new(|| widget::Id::new("permanent-delete-button")); @@ -488,7 +491,7 @@ impl AsRef for ArchiveType { #[derive(Clone, Debug)] pub enum DialogPage { Compress { - paths: Vec, + paths: Box<[PathBuf]>, to: PathBuf, name: String, archive_type: ArchiveType, @@ -528,7 +531,7 @@ pub enum DialogPage { store_opt: Option, }, PermanentlyDelete { - paths: Vec, + paths: Box<[PathBuf]>, }, RenameItem { from: PathBuf, @@ -632,7 +635,7 @@ pub enum WindowKind { Desktop(Entity), DesktopViewOptions, Dialogs(widget::Id), - FileDialog(Option>), + FileDialog(Option>), Preview(Option, PreviewKind), } @@ -719,7 +722,7 @@ impl App { fn push_dialog(&mut self, page: DialogPage, focus_id: Option) -> Task { let t = self.dialog_pages.push_back(page); if let Some(focus_id) = focus_id { - Task::batch(vec![t, focus(focus_id)]) + Task::batch([t, focus(focus_id)]) } else { t } @@ -733,17 +736,14 @@ impl App { // player that is passed every path. let mut groups: FxHashMap> = FxHashMap::default(); let mut all_archives = true; - let supported_archive_types = crate::archive::SUPPORTED_ARCHIVE_TYPES - .iter() - .filter_map(|mime_type| mime_type.parse::().ok()) - .collect::>(); + let supported_archive_types = crate::archive::SUPPORTED_ARCHIVE_TYPES; for (mime, path) in paths.iter().map(|path| { ( mime_icon::mime_for_path(path, None, false), path.as_ref().to_owned(), ) }) { - if !supported_archive_types.contains(&mime) { + if all_archives && !supported_archive_types.iter().copied().any(|t| mime == t) { all_archives = false; } groups.entry(mime).or_default().push(path); @@ -772,7 +772,7 @@ impl App { io::ErrorKind::PermissionDenied => { // If permission is denied, try marking as executable, then running tasks.push(self.push_dialog( - DialogPage::SetExecutableAndLaunch { path: path.clone() }, + DialogPage::SetExecutableAndLaunch { path }, Some(SET_EXECUTABLE_AND_LAUNCH_CONFIRM_BUTTON_ID.clone()), )); } @@ -905,11 +905,7 @@ impl App { match exec.next() { Some(cmd) if !cmd.contains('=') => { let mut proc = tokio::process::Command::new(cmd); - for arg in exec { - if !arg.starts_with('%') { - proc.arg(arg); - } - } + proc.args(exec.filter(|arg| !arg.starts_with('%'))); let _ = proc.spawn(); } _ => (), @@ -957,7 +953,7 @@ impl App { .keys() .map(|k| (*k, (0., 0., 0., 0.))) .collect(); - let mut sorted_overlaps: Vec<_> = self.overlap.values().collect(); + let mut sorted_overlaps: Box<[_]> = self.overlap.values().collect(); sorted_overlaps .sort_by(|a, b| (b.1.width * b.1.height).total_cmp(&(a.1.width * b.1.height))); @@ -1105,7 +1101,7 @@ impl App { } // This wrapper ensures that local folders use trash and remote folders permanently delete with a dialog - fn delete(&mut self, paths: Vec) -> Task { + fn delete(&mut self, paths: impl IntoIterator) -> Task { let mut dialog_paths = Vec::new(); let mut trash_paths = Vec::new(); @@ -1129,7 +1125,7 @@ impl App { if !dialog_paths.is_empty() { tasks.push(self.update(Message::DialogPush( DialogPage::PermanentlyDelete { - paths: dialog_paths, + paths: dialog_paths.into_boxed_slice(), }, Some(PERMANENT_DELETE_BUTTON_ID.clone()), ))); @@ -1247,64 +1243,61 @@ impl App { let icon_sizes = self.config.tab.icon_sizes; let mounter_items = self.mounter_items.clone(); - Task::perform( - async move { - let location2 = location.clone(); - match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { - Ok((parent_item_opt, mut items)) => { - #[cfg(feature = "gvfs")] - { - let mounter_paths: Vec<_> = mounter_items - .iter() - .flat_map(|item| item.1.iter()) - .filter_map(MounterItem::path) - .collect(); - if !mounter_paths.is_empty() { - for item in &mut items { - item.is_mount_point = - item.path_opt().is_some_and(|p| mounter_paths.contains(p)); - } + Task::future(async move { + let location2 = location.clone(); + match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { + Ok((parent_item_opt, mut items)) => { + #[cfg(feature = "gvfs")] + { + let mounter_paths: Box<[_]> = mounter_items + .values() + .flatten() + .filter_map(MounterItem::path) + .collect(); + if !mounter_paths.is_empty() { + for item in &mut items { + item.is_mount_point = + item.path_opt().is_some_and(|p| mounter_paths.contains(p)); } } + } - cosmic::action::app(Message::TabRescan( - entity, - location, - parent_item_opt, - items, - selection_paths, - )) - } - Err(err) => { - log::warn!("failed to rescan: {err}"); - cosmic::action::none() - } + cosmic::action::app(Message::TabRescan( + entity, + location, + parent_item_opt, + items, + selection_paths, + )) } - }, - |x| x, - ) + Err(err) => { + log::warn!("failed to rescan: {err}"); + cosmic::action::none() + } + } + }) } fn rescan_trash(&mut self) -> Task { - let mut needs_reload = Vec::new(); - for entity in self.tab_model.iter() { - if let Some(tab) = self.tab_model.data::(entity) { - if matches!(&tab.location, Location::Trash) { - needs_reload.push((entity, Location::Trash)); - } - } - } + let needs_reload: Box<[_]> = self + .tab_model + .iter() + .filter_map(|entity| { + let tab = self.tab_model.data::(entity)?; + (tab.location == Location::Trash).then_some((entity, Location::Trash)) + }) + .collect(); + + let commands = needs_reload + .into_iter() + .map(|(entity, location)| self.update_tab(entity, location, None)); - let mut commands = Vec::with_capacity(needs_reload.len()); - for (entity, location) in needs_reload { - commands.push(self.update_tab(entity, location, None)); - } Task::batch(commands) } /// Refresh all tabs that are opened in [`Location::Recents`]. fn refresh_recents_tabs(&mut self) -> Task { - let commands: Vec<_> = self + let commands: Box<[_]> = self .tab_model .iter() .filter_map(|entity| { @@ -1312,27 +1305,28 @@ impl App { (tab.location == Location::Recents).then_some(entity) }) .collect(); - let commands: Vec<_> = commands + + let commands = commands .into_iter() - .map(|entity| self.update_tab(entity, Location::Recents, None)) - .collect(); + .map(|entity| self.update_tab(entity, Location::Recents, None)); + Task::batch(commands) } fn rescan_recents(&mut self) -> Task { - let mut needs_reload = Vec::new(); - for entity in self.tab_model.iter() { - if let Some(tab) = self.tab_model.data::(entity) { - if matches!(&tab.location, Location::Recents) { - needs_reload.push((entity, Location::Recents)); - } - } - } + let needs_reload: Box<[_]> = self + .tab_model + .iter() + .filter_map(|entity| { + let tab = self.tab_model.data::(entity)?; + (tab.location == Location::Recents).then_some((entity, Location::Recents)) + }) + .collect(); + + let commands = needs_reload + .into_iter() + .map(|(entity, location)| self.update_tab(entity, location, None)); - let mut commands = Vec::with_capacity(needs_reload.len()); - for (entity, location) in needs_reload { - commands.push(self.update_tab(entity, location, None)); - } Task::batch(commands) } @@ -1396,17 +1390,19 @@ impl App { Task::none() } - fn selected_paths(&self, entity_opt: Option) -> Vec { - let mut paths = Vec::new(); + fn selected_paths( + &self, + entity_opt: Option, + ) -> impl Iterator + use<'_> { let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); - if let Some(tab) = self.tab_model.data::(entity) { - for location in tab.selected_locations() { - if let Some(path) = location.path_opt() { - paths.push(path.clone()); - } - } - } - paths + self.tab_model + .data::(entity) + .into_iter() + .flat_map(|tab| { + tab.selected_locations() + .into_iter() + .filter_map(Location::into_path_opt) + }) } fn set_cut(&mut self, entity_opt: Option) { @@ -1419,32 +1415,33 @@ impl App { fn update_config(&mut self) -> Task { self.update_nav_model(); // Tabs are collected first to placate the borrowck - let tabs: Vec<_> = self.tab_model.iter().collect(); + let tabs: Box<[_]> = self.tab_model.iter().collect(); // Update main conf and each tab with the new config - let commands: Vec<_> = - std::iter::once(cosmic::command::set_theme(self.config.app_theme.theme())) - .chain(tabs.into_iter().map(|entity| { - self.update(Message::TabMessage( - Some(entity), - tab::Message::Config(self.config.tab), - )) - })) - .collect(); + let commands = std::iter::once(cosmic::command::set_theme(self.config.app_theme.theme())) + .chain(tabs.into_iter().map(|entity| { + self.update(Message::TabMessage( + Some(entity), + tab::Message::Config(self.config.tab), + )) + })); Task::batch(commands) } fn update_desktop(&mut self) -> Task { - let mut needs_reload = Vec::new(); - for entity in self.tab_model.iter() { - if let Some(tab) = self.tab_model.data::(entity) { + let needs_reload: Box<[_]> = (self.tab_model.iter()) + .filter_map(|entity| { + let tab = self.tab_model.data::(entity)?; if let Location::Desktop(path, output, _) = &tab.location { - needs_reload.push(( + Some(( entity, Location::Desktop(path.clone(), output.clone(), self.config.desktop), - )); + )) + } else { + None } - } - } + }) + .collect(); + let mut commands = Vec::with_capacity(needs_reload.len()); for (entity, location) in needs_reload { if let Some(tab) = self.tab_model.data_mut::(entity) { @@ -1536,9 +1533,7 @@ impl App { // Collect all mounter items let mut nav_items = Vec::new(); for (key, items) in &self.mounter_items { - for item in items { - nav_items.push((*key, item)); - } + nav_items.extend(items.iter().map(|item| (*key, item))); } // Sort by name lexically nav_items.sort_by(|a, b| LANGUAGE_SORTER.compare(&a.1.name(), &b.1.name())); @@ -1578,20 +1573,17 @@ impl App { if self.pending_operations.is_empty() { #[cfg(feature = "notify")] if let Some(notification_arc) = self.notification_opt.take() { - return Task::perform( - async move { - tokio::task::spawn_blocking(move || { - //TODO: this is nasty - let notification_mutex = Arc::try_unwrap(notification_arc).unwrap(); - let notification = notification_mutex.into_inner().unwrap(); - notification.close(); - }) - .await - .unwrap(); - cosmic::action::app(Message::MaybeExit) - }, - |x| x, - ); + return Task::future(async move { + tokio::task::spawn_blocking(move || { + //TODO: this is nasty + let notification_mutex = Arc::try_unwrap(notification_arc).unwrap(); + let notification = notification_mutex.into_inner().unwrap(); + notification.close(); + }) + .await + .unwrap(); + cosmic::action::app(Message::MaybeExit) + }); } } @@ -1612,14 +1604,14 @@ impl App { fn update_watcher(&mut self) -> Task { if let Some((mut watcher, old_paths)) = self.watcher_opt.take() { - let mut new_paths = FxHashSet::default(); - for entity in self.tab_model.iter() { - if let Some(tab) = self.tab_model.data::(entity) { - if let Some(path) = tab.location.path_opt() { - new_paths.insert(path.clone()); - } - } - } + let new_paths: FxHashSet<_> = self + .tab_model + .iter() + .filter_map(|entity| { + let tab = self.tab_model.data::(entity)?; + tab.location.path_opt().cloned() + }) + .collect(); // Unwatch paths no longer used for path in &old_paths { @@ -1679,7 +1671,7 @@ impl App { table = table.push(widget::divider::horizontal::light()); } } - widget::column::with_children(vec![ + widget::column::with_children([ widget::text::body(fl!("network-drive-description")).into(), table.into(), ]) @@ -1693,7 +1685,7 @@ impl App { } = theme::active().cosmic().spacing; let config = self.config.desktop; - let mut children = Vec::new(); + let mut column = widget::column::with_capacity(2); let mut section = widget::settings::section().title(fl!("show-on-desktop")); section = section.add( @@ -1729,17 +1721,17 @@ impl App { }, ), ); - children.push(section.into()); + column = column.push(section); let mut section = widget::settings::section().title(fl!("icon-size-and-spacing")); - let icon_size: u16 = config.icon_size.into(); + let icon_size = config.icon_size; section = section.add( widget::settings::item::builder(fl!("icon-size")) .description(format!("{icon_size}%")) .control( - widget::slider(50..=500, icon_size, move |icon_size| { + widget::slider(50..=500, icon_size.get(), move |_| { Message::DesktopConfig(DesktopConfig { - icon_size: NonZeroU16::new(icon_size).unwrap(), + icon_size, ..config }) }) @@ -1747,23 +1739,23 @@ impl App { ), ); - let grid_spacing: u16 = config.grid_spacing.into(); + let grid_spacing = config.grid_spacing; section = section.add( widget::settings::item::builder(fl!("grid-spacing")) .description(format!("{grid_spacing}%")) .control( - widget::slider(50..=500, grid_spacing, move |grid_spacing| { + widget::slider(50..=500, grid_spacing.get(), move |_| { Message::DesktopConfig(DesktopConfig { - grid_spacing: NonZeroU16::new(grid_spacing).unwrap(), + grid_spacing, ..config }) }) .step(25u16), ), ); - children.push(section.into()); + column = column.push(section); - widget::column::with_children(children) + column .padding([0, space_l, space_l, space_l]) .spacing(space_m) .into() @@ -1781,8 +1773,8 @@ impl App { let mut section = widget::settings::section().title(fl!("pending")); for (id, (op, controller)) in self.pending_operations.iter().rev() { let progress = controller.progress(); - section = section.add(widget::column::with_children(vec![ - widget::row::with_children(vec![ + section = section.add(widget::column::with_children([ + widget::row::with_children([ widget::progress_bar(0.0..=1.0, progress) .height(progress_bar_height) .into(), @@ -1828,9 +1820,9 @@ impl App { if !self.failed_operations.is_empty() { let mut section = widget::settings::section().title(fl!("failed")); - for (_id, (op, controller, error)) in self.failed_operations.iter().rev() { + for (op, controller, error) in self.failed_operations.values().rev() { let progress = controller.progress(); - section = section.add(widget::column::with_children(vec![ + section = section.add(widget::column::with_children([ widget::text::body(op.pending_text(progress, controller.state())).into(), widget::text::body(error).into(), ])); @@ -1840,7 +1832,7 @@ impl App { if !self.complete_operations.is_empty() { let mut section = widget::settings::section().title(fl!("complete")); - for (_id, op) in self.complete_operations.iter().rev() { + for op in self.complete_operations.values().rev() { section = section.add(widget::text::body(op.completed_text())); } children.push(section.into()); @@ -1984,50 +1976,49 @@ impl App { let mut dedupe = FxHashSet::default(); // start with exact matches - for mime_app in self.mime_app_cache.get(mime_type) { - let app_id = &mime_app.id; - if !dedupe.contains(app_id) { - results.push((mime_app, MimeAppMatch::Exact)); - dedupe.insert(app_id); - } - } + results.extend( + self.mime_app_cache + .get(mime_type) + .iter() + .filter(|&mime_app| dedupe.insert(&mime_app.id)) + .map(|mime_app| (mime_app, MimeAppMatch::Exact)), + ); // grab matches based off of subclass / parent mime type if let Some(parent_types) = mime_icon::parent_mime_types(mime_type) { for parent_type in parent_types { - for mime_app in self.mime_app_cache.get(&parent_type) { - let app_id = &mime_app.id; - if !dedupe.contains(app_id) { - results.push((mime_app, MimeAppMatch::Related)); - dedupe.insert(app_id); - } - } + results.extend( + self.mime_app_cache + .get(&parent_type) + .iter() + .filter(|&mime_app| dedupe.insert(&mime_app.id)) + .map(|mime_app| (mime_app, MimeAppMatch::Related)), + ); } } // Add other apps - for mime_app in self.mime_app_cache.apps() { - let app_id = &mime_app.id; - if !dedupe.contains(app_id) { - results.push((mime_app, MimeAppMatch::Other)); - dedupe.insert(app_id); - } - } + results.extend( + self.mime_app_cache + .apps() + .iter() + .filter(|&mime_app| dedupe.insert(&mime_app.id)) + .map(|mime_app| (mime_app, MimeAppMatch::Other)), + ); results } // Update favorites based on renaming or moving dirs. - fn update_favorites(&mut self, path_changes: &[(PathBuf, PathBuf)]) -> bool { + fn update_favorites(&mut self, path_changes: &[(impl AsRef, impl AsRef)]) -> bool { let mut favorites_changed = false; let favorites = self .config .favorites .iter() - .cloned() .map(|favorite| { - if let Favorite::Path(ref path) = favorite { - for (from, to) in path_changes { + if let Favorite::Path(path) = favorite { + for (from, to) in path_changes.iter().map(|(f, t)| (f.as_ref(), t.as_ref())) { if path.starts_with(from) { if let Ok(relative) = path.strip_prefix(from) { favorites_changed = true; @@ -2036,7 +2027,7 @@ impl App { } } } - favorite + favorite.clone() }) .collect(); @@ -2216,7 +2207,7 @@ impl Application for App { for location in flags.uris { if let Some(e) = app.nav_model.iter().find(|e| { app.nav_model.data::(*e).is_some_and( - |l| matches!(l, Location::Network(uri, ..) if *uri == location.to_string()), + |l| matches!(l, Location::Network(uri, ..) if *uri == *location.as_str()), ) }) { commands.push(cosmic::task::message(cosmic::Action::App( @@ -2225,7 +2216,7 @@ impl Application for App { } } - if app.tab_model.iter().next().is_none() { + if app.tab_model.entity_at(0).is_none() { if let Ok(current_dir) = env::current_dir() { commands.push(app.open_tab(Location::Path(current_dir), true, None)); } else { @@ -2278,10 +2269,10 @@ impl Application for App { let favorite_index_opt = self.nav_model.data::(entity); let location_opt = self.nav_model.data::(entity); - let mut items = Vec::new(); + let mut items = Vec::with_capacity(7); if location_opt - .and_then(|x| x.path_opt()) + .and_then(Location::path_opt) .is_some_and(|x| x.is_file()) { items.push(cosmic::widget::menu::Item::Button( @@ -2569,13 +2560,13 @@ impl Application for App { if let Location::Network(uri, _, _) = tab .items_opt .as_ref() - .and_then(|items| items.iter().find(|i| i.path_opt() == Some(&path))) + .and_then(|items| items.iter().find(|&i| i.path_opt() == Some(&path))) .unwrap() .location_opt - .clone() + .as_ref() .unwrap() { - Some((uri, name, path.clone())) + Some((uri.clone(), name, path.clone())) } else { None } @@ -2586,7 +2577,7 @@ impl Application for App { } else { Favorite::from_path(path) }; - if !favorites.iter().any(|f| f == &favorite) { + if !favorites.contains(&favorite) { favorites.push(favorite); } } @@ -2598,7 +2589,7 @@ impl Application for App { return self.update_config(); } Message::Compress(entity_opt) => { - let paths = self.selected_paths(entity_opt); + let paths: Box<[_]> = self.selected_paths(entity_opt).collect(); if let Some(current_path) = paths.first() { if let Some(destination) = current_path.parent().zip(current_path.file_stem()) { let to = destination.0.to_path_buf(); @@ -2634,13 +2625,13 @@ impl Application for App { } } let paths = self.selected_paths(entity_opt); - let contents = ClipboardCopy::new(ClipboardKind::Copy, &paths); + let contents = ClipboardCopy::new(ClipboardKind::Copy, paths); return clipboard::write_data(contents); } Message::Cut(entity_opt) => { self.set_cut(entity_opt); let paths = self.selected_paths(entity_opt); - let contents = ClipboardCopy::new(ClipboardKind::Cut { is_dnd: false }, &paths); + let contents = ClipboardCopy::new(ClipboardKind::Cut { is_dnd: false }, paths); return clipboard::write_data(contents); } Message::CloseToast(id) => { @@ -2678,7 +2669,7 @@ impl Application for App { } } } else { - let paths = self.selected_paths(entity_opt); + let paths: Box<[_]> = self.selected_paths(entity_opt).collect(); if !paths.is_empty() { return self.delete(paths); } @@ -2741,12 +2732,11 @@ impl Application for App { return command.map(|_id| cosmic::Action::None); } - let mut tasks = Vec::new(); - for (id, kind) in &self.windows { - if matches!(kind, WindowKind::Dialogs(_)) { - tasks.push(window::close(*id)); - } - } + let tasks = self + .windows + .iter() + .filter(|(_, kind)| matches!(*kind, WindowKind::Dialogs(_))) + .map(|(id, _)| window::close(*id)); return Task::batch(tasks); } } @@ -2770,7 +2760,7 @@ impl Application for App { let name = format!("{name}{extension}"); let to = to.join(name); tasks.push(self.operation(Operation::Compress { - paths, + paths: paths.into_vec(), to, archive_type, password, @@ -2809,13 +2799,10 @@ impl Application for App { auth, auth_tx, } => { - tasks.push(Task::perform( - async move { - auth_tx.send(auth).await.unwrap(); - cosmic::action::none() - }, - |x| x, - )); + tasks.push(Task::future(async move { + auth_tx.send(auth).await.unwrap(); + cosmic::action::none() + })); } DialogPage::NetworkError { mounter_key: _, @@ -2915,7 +2902,7 @@ impl Application for App { ]); } Message::ExtractHere(entity_opt) => { - let paths = self.selected_paths(entity_opt); + let paths: Box<[_]> = self.selected_paths(entity_opt).collect(); if let Some(destination) = paths .first() .and_then(|first| first.parent()) @@ -2929,7 +2916,8 @@ impl Application for App { } } Message::ExtractTo(entity_opt) => { - return self.extract_to(&self.selected_paths(entity_opt)); + let selected_paths: Box<[_]> = self.selected_paths(entity_opt).collect(); + return self.extract_to(&selected_paths); } Message::ExtractToResult(result) => { match result { @@ -2990,20 +2978,17 @@ impl Application for App { } 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(), - ); + let location = tab + .edit_location + .as_ref() + .map_or_else(|| &tab.location, |x| &x.location); // 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.to_string_lossy().into_owned(); path_string.push_str(&text); - tab.edit_location = Some( - location - .with_path(PathBuf::from(path_string)) - .into(), - ); + tab.edit_location = + Some(location.with_path(path_string.into()).into()); } } } @@ -3062,23 +3047,21 @@ impl Application for App { let mut commands = Vec::new(); { let home_location = Location::Path(home_dir()); - let entities: Vec<_> = self.tab_model.iter().collect(); + let entities: Box<[_]> = self.tab_model.iter().collect(); for entity in entities { - let title_opt = match self.tab_model.data_mut::(entity) { - Some(tab) => { - if unmounted.iter().any(|unmounted| { + let title_opt = self.tab_model.data_mut::(entity).and_then(|tab| { + unmounted + .iter() + .any(|unmounted| { tab.location .path_opt() .is_some_and(|location| location.starts_with(unmounted)) - }) { + }) + .then(|| { tab.change_location(&home_location, None); - Some(tab.title()) - } else { - None - } - } - None => None, - }; + tab.title() + }) + }); if let Some(title) = title_opt { self.tab_model.text_set(entity, title); commands.push(self.update_tab(entity, home_location.clone(), None)); @@ -3150,7 +3133,11 @@ impl Application for App { ); } Message::NetworkResult(mounter_key, uri, res) => { - if self.network_drive_connecting == Some((mounter_key, uri.clone())) { + if self + .network_drive_connecting + .as_ref() + .is_some_and(|(m, u)| *m == mounter_key && *u == uri) + { self.network_drive_connecting = None; } match res { @@ -3176,10 +3163,10 @@ impl Application for App { Message::NewItem(entity_opt, dir) => { let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); if let Some(tab) = self.tab_model.data_mut::(entity) { - if let Some(path) = &tab.location.path_opt() { + if let Some(path) = tab.location.path_opt() { return Task::batch([ self.dialog_pages.push_back(DialogPage::NewItem { - parent: (*path).clone(), + parent: path.clone(), name: String::new(), dir, }), @@ -3196,10 +3183,10 @@ impl Application for App { log::debug!("{events:?}"); let mut needs_reload = Vec::new(); - let entities: Vec<_> = self.tab_model.iter().collect(); + let entities: Box<[_]> = self.tab_model.iter().collect(); for entity in entities { if let Some(tab) = self.tab_model.data_mut::(entity) { - if let Some(path) = &tab.location.path_opt() { + if let Some(path) = tab.location.path_opt() { let mut contains_change = false; for event in &events { for event_path in &event.paths { @@ -3253,10 +3240,9 @@ impl Application for App { } } - let mut commands = Vec::with_capacity(needs_reload.len()); - for (entity, location) in needs_reload { - commands.push(self.update_tab(entity, location, None)); - } + let commands = needs_reload + .into_iter() + .map(|(entity, location)| self.update_tab(entity, location, None)); return Task::batch(commands); } Message::NotifyWatcher(mut watcher_wrapper) => match watcher_wrapper.watcher_opt.take() @@ -3271,21 +3257,21 @@ impl Application for App { }, Message::OpenTerminal(entity_opt) => { if let Some(terminal) = self.mime_app_cache.terminal() { - let mut paths = Vec::new(); + let mut paths = Box::from([]); let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); if let Some(tab) = self.tab_model.data_mut::(entity) { - if let Some(path) = &tab.location.path_opt() { + if let Some(path) = tab.location.path_opt() { if let Some(items) = tab.items_opt() { - for item in items { - if item.selected { - if let Some(path) = item.path_opt() { - paths.push(path.clone()); - } - } - } + paths = + items + .iter() + .filter_map(|item| { + if item.selected { item.path_opt() } else { None } + }) + .collect(); } if paths.is_empty() { - paths.push((*path).clone()); + paths = Box::from([path]); } } } @@ -3294,7 +3280,7 @@ impl Application for App { .command::<&str>(&[]) .and_then(|v| v.into_iter().next()) { - command.current_dir(&path); + command.current_dir(path); if let Err(err) = spawn_detached(&mut command) { log::warn!( "failed to open {} with terminal {:?}: {}", @@ -3310,20 +3296,19 @@ impl Application for App { } } Message::OpenInNewTab(entity_opt) => { - return Task::batch(self.selected_paths(entity_opt).into_iter().filter_map( - |path| { - if path.is_dir() { - Some(self.open_tab(Location::Path(path), false, None)) - } else { - None - } - }, - )); + let selected_paths: Box<[_]> = self + .selected_paths(entity_opt) + .filter(|p| p.is_dir()) + .collect(); + return Task::batch( + selected_paths + .into_iter() + .map(|path| self.open_tab(Location::Path(path), false, None)), + ); } Message::OpenInNewWindow(entity_opt) => match env::current_exe() { Ok(exe) => self .selected_paths(entity_opt) - .into_iter() .filter(|p| p.is_dir()) .for_each(|path| match process::Command::new(&exe).arg(path).spawn() { Ok(_child) => {} @@ -3336,13 +3321,12 @@ impl Application for App { } }, Message::OpenItemLocation(entity_opt) => { - return Task::batch(self.selected_paths(entity_opt).into_iter().filter_map( - |path| { - path.parent().map(Path::to_path_buf).map(|parent| { - self.open_tab(Location::Path(parent), true, Some(vec![path])) - }) - }, - )); + let selected_paths: Box<[_]> = self.selected_paths(entity_opt).collect(); + return Task::batch(selected_paths.into_iter().filter_map(|path| { + path.parent() + .map(Path::to_path_buf) + .map(|parent| self.open_tab(Location::Path(parent), true, Some(vec![path]))) + })); } Message::OpenWithBrowse => match self.dialog_pages.pop_front() { Some(( @@ -3428,7 +3412,7 @@ impl Application for App { } } Message::PasteContents(to, mut contents) => { - contents.paths.retain(|p| p != &to); + contents.paths.retain(|p| *p != to); if !contents.paths.is_empty() { return match contents.kind { ClipboardKind::Copy => self.operation(Operation::Copy { @@ -3483,18 +3467,16 @@ impl Application for App { // If a favorite for a path has been renamed or moved, update it. if let Operation::Rename { ref from, ref to } = op { - if self.update_favorites(&[(from.clone(), to.clone())]) { + if self.update_favorites([(from, to)].as_slice()) { commands.push(self.update_config()); } } else if let Operation::Move { ref paths, ref to, .. } = op { - let path_changes: Vec<_> = paths + let path_changes: Box<[_]> = paths .iter() - .filter_map(|from| { - from.file_name().map(|name| (from.clone(), to.join(name))) - }) + .filter_map(|from| from.file_name().map(|name| (from, to.join(name)))) .collect(); if self.update_favorites(&path_changes) { commands.push(self.update_config()); @@ -3510,8 +3492,8 @@ impl Application for App { // Close progress notification if all relevant operations are finished if !self .pending_operations - .iter() - .any(|(_id, (op, _))| op.show_progress_notification()) + .values() + .any(|(op, _)| op.show_progress_notification()) { self.progress_operations.clear(); } @@ -3550,8 +3532,8 @@ impl Application for App { // Close progress notification if all relevant operations are finished if !self .pending_operations - .iter() - .any(|(_id, (op, _))| op.show_progress_notification()) + .values() + .any(|(op, _)| op.show_progress_notification()) { self.progress_operations.clear(); } @@ -3578,7 +3560,7 @@ impl Application for App { } } Message::PermanentlyDelete(entity_opt) => { - let paths = self.selected_paths(entity_opt); + let paths: Box<[_]> = self.selected_paths(entity_opt).collect(); if !paths.is_empty() { return self.push_dialog( DialogPage::PermanentlyDelete { paths }, @@ -3595,7 +3577,7 @@ impl Application for App { return cosmic::task::message(Message::SetShowDetails(show_details)); } Mode::Desktop => { - let selected_paths = self.selected_paths(entity_opt); + let selected_paths: Box<[_]> = self.selected_paths(entity_opt).collect(); let mut commands = Vec::with_capacity(selected_paths.len()); for path in selected_paths { let mut settings = window::Settings { @@ -3629,7 +3611,7 @@ impl Application for App { } } Message::RemoveFromRecents(entity_opt) => { - let paths = self.selected_paths(entity_opt); + let paths: Box<[_]> = self.selected_paths(entity_opt).collect(); return self.operation(Operation::RemoveFromRecents { paths }); } Message::RescanRecents => { @@ -3653,35 +3635,34 @@ impl Application for App { let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); if let Some(tab) = self.tab_model.data_mut::(entity) { if let Some(items) = tab.items_opt() { - let mut selected = Vec::new(); - for item in items { - if item.selected { - if let Some(path) = item.path_opt() { - selected.push(path.clone()); + let selected: Box<[_]> = items + .iter() + .filter_map(|item| { + if item.selected { + item.path_opt().cloned() + } else { + None } - } - } + }) + .collect(); if !selected.is_empty() { //TODO: batch rename - let mut tasks = Vec::new(); - for path in selected { - let parent = match path.parent() { - Some(some) => some.to_path_buf(), - None => continue, - }; - let name = match path.file_name().and_then(|x| x.to_str()) { - Some(some) => some.to_string(), - None => continue, - }; - let dir = path.is_dir(); - tasks.push(self.dialog_pages.push_back(DialogPage::RenameItem { - from: path, - parent, - name, - dir, - })); - } - tasks.push(widget::text_input::focus(self.dialog_text_input.clone())); + let tasks = selected + .into_iter() + .filter_map(|path| { + let parent = path.parent()?.to_path_buf(); + let name = path.file_name()?.to_str()?.to_string(); + let dir = path.is_dir(); + Some(self.dialog_pages.push_back(DialogPage::RenameItem { + from: path, + parent, + name, + dir, + })) + }) + .chain(std::iter::once(widget::text_input::focus( + self.dialog_text_input.clone(), + ))); return Task::batch(tasks); } } @@ -3691,13 +3672,10 @@ impl Application for App { if let Some((dialog_page, task)) = self.dialog_pages.pop_front() { match dialog_page { DialogPage::Replace { tx, .. } => { - return Task::perform( - async move { - let _ = tx.send(replace_result).await; - cosmic::action::none() - }, - |x| x, - ); + return Task::future(async move { + let _ = tx.send(replace_result).await; + cosmic::action::none() + }); } other => { log::warn!("tried to send replace result to the wrong dialog"); @@ -3786,15 +3764,15 @@ impl Application for App { return Task::batch(tasks); } Message::TabNext => { - let len = self.tab_model.iter().count(); + let len = self.tab_model.len(); let pos = self .tab_model .position(self.tab_model.active()) + .expect("should always be at least one tab open") // Wraparound to 0 if i + 1 > num of tabs - .map(|i| (i as usize + 1) % len) - .expect("should always be at least one tab open"); + + 1 % len as u16; - let entity = self.tab_model.iter().nth(pos); + let entity = self.tab_model.entity_at(pos); if let Some(entity) = entity { return self.update(Message::TabActivate(entity)); } @@ -3803,17 +3781,12 @@ impl Application for App { let pos = self .tab_model .position(self.tab_model.active()) - .and_then(|i| (i as usize).checked_sub(1)) + .expect("should always be at least one tab open") + .checked_sub(1) // Subtraction underflow => last tab; i.e. it wraps around - .unwrap_or_else(|| { - self.tab_model - .iter() - .count() - .checked_sub(1) - .unwrap_or_default() - }); + .unwrap_or_else(|| (self.tab_model.len() as u16).saturating_sub(1)); - let entity = self.tab_model.iter().nth(pos); + let entity = self.tab_model.entity_at(pos); if let Some(entity) = entity { return self.update(Message::TabActivate(entity)); } @@ -3892,7 +3865,7 @@ impl Application for App { tab::Command::AddToSidebar(path) => { let mut favorites = self.config.favorites.clone(); let favorite = Favorite::from_path(path); - if !favorites.iter().any(|f| f == &favorite) { + if !favorites.contains(&favorite) { favorites.push(favorite); } config_set!(favorites, favorites); @@ -4006,7 +3979,7 @@ impl Application for App { } tab::Command::OpenFile(paths) => commands.push(self.open_file(&paths)), tab::Command::OpenInNewTab(path) => { - commands.push(self.open_tab(Location::Path(path.clone()), false, None)); + commands.push(self.open_tab(Location::Path(path), false, None)); } tab::Command::OpenInNewWindow(path) => match env::current_exe() { Ok(exe) => match process::Command::new(&exe).arg(path).spawn() { @@ -4082,13 +4055,10 @@ impl Application for App { if !self.must_save_sort_names & changed { self.must_save_sort_names = true; - return cosmic::Task::perform( - async move { - tokio::time::sleep(Duration::from_secs(1)).await; - cosmic::action::app(Message::SaveSortNames) - }, - |x| x, - ); + return cosmic::Task::future(async move { + tokio::time::sleep(Duration::from_secs(1)).await; + cosmic::action::app(Message::SaveSortNames) + }); } } } @@ -4212,10 +4182,7 @@ impl Application for App { self.core.set_main_window_id(None); return Task::batch([ window::close(window_id), - Task::perform( - async move { cosmic::action::app(Message::MaybeExit) }, - |x| x, - ), + Task::future(async move { cosmic::action::app(Message::MaybeExit) }), ]); } } @@ -4240,58 +4207,23 @@ impl Application for App { let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); let mut config = self.config.tab; if let Some(tab) = self.tab_model.data::(entity) { - match tab.config.view { - tab::View::List => config.icon_sizes.list = 100.try_into().unwrap(), - tab::View::Grid => config.icon_sizes.grid = 100.try_into().unwrap(), - } + zoom_to_default(tab.config.view, &mut config.icon_sizes); } return self.update(Message::TabConfig(config)); } Message::ZoomIn(entity_opt) => { let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); - let zoom_in = |size: &mut NonZeroU16, min: u16, max: u16| { - let mut step = min; - while step <= max { - if size.get() < step { - *size = step.try_into().unwrap(); - break; - } - step += 25; - } - if size.get() > step { - *size = step.try_into().unwrap(); - } - }; let mut config = self.config.tab; if let Some(tab) = self.tab_model.data::(entity) { - match tab.config.view { - tab::View::List => zoom_in(&mut config.icon_sizes.list, 50, 500), - tab::View::Grid => zoom_in(&mut config.icon_sizes.grid, 50, 500), - } + zoom_in_view(tab.config.view, &mut config.icon_sizes); } return self.update(Message::TabConfig(config)); } Message::ZoomOut(entity_opt) => { let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); - let zoom_out = |size: &mut NonZeroU16, min: u16, max: u16| { - let mut step = max; - while step >= min { - if size.get() > step { - *size = step.try_into().unwrap(); - break; - } - step -= 25; - } - if size.get() < step { - *size = step.try_into().unwrap(); - } - }; let mut config = self.config.tab; if let Some(tab) = self.tab_model.data::(entity) { - match tab.config.view { - tab::View::List => zoom_out(&mut config.icon_sizes.list, 50, 500), - tab::View::Grid => zoom_out(&mut config.icon_sizes.grid, 50, 500), - } + zoom_out_view(tab.config.view, &mut config.icon_sizes); } return self.update(Message::TabConfig(config)); } @@ -4435,8 +4367,8 @@ impl Application for App { if let Some(path) = self .nav_model .data::(entity) - .and_then(|x| x.path_opt()) - .map(ToOwned::to_owned) + .and_then(Location::path_opt) + .cloned() { return self.open_file(&[path]); } @@ -4445,7 +4377,7 @@ impl Application for App { if let Some(path) = self .nav_model .data::(entity) - .and_then(|x| x.path_opt()) + .and_then(Location::path_opt) .cloned() { match tab::item_from_path(&path, IconSizes::default()) { @@ -4544,7 +4476,7 @@ impl Application for App { if let Some(path) = self .nav_model .data::(entity) - .and_then(|location| location.path_opt()) + .and_then(Location::path_opt) { match tab::item_from_path(path, IconSizes::default()) { Ok(item) => { @@ -4588,7 +4520,8 @@ impl Application for App { Message::OutputEvent(output_event, output) => { match output_event { OutputEvent::Created(output_info_opt) => { - log::info!("output {}: created", output.id()); + let output_id = output.id(); + log::info!("output {output_id}: created"); let surface_id = WindowId::unique(); if let Some(old_surface_id) = @@ -4596,9 +4529,7 @@ impl Application for App { { //TODO: remove old surface? log::warn!( - "output {}: already had surface ID {:?}", - output.id(), - old_surface_id + "output {output_id}: already had surface ID {old_surface_id:?}" ); } @@ -4609,12 +4540,12 @@ impl Application for App { output_name } None => { - log::warn!("output {}: no output name", output.id()); + log::warn!("output {output_id}: no output name"); String::new() } }, None => { - log::warn!("output {}: no output info", output.id()); + log::warn!("output {output_id}: no output info"); String::new() } }; @@ -4705,14 +4636,14 @@ impl Application for App { Message::Eject => { #[cfg(feature = "gvfs")] { - let paths = self.selected_paths(None); - if let Some(p) = paths.first() { + let mut paths = self.selected_paths(None); + if let Some(p) = paths.next() { { for (k, mounter_items) in &self.mounter_items { if let Some(mounter) = MOUNTERS.get(k) { if let Some(item) = mounter_items .iter() - .find(|item| item.path().is_some_and(|path| path == *p)) + .find(|&item| item.path().is_some_and(|path| path == p)) { return mounter .unmount(item.clone()) @@ -4742,9 +4673,9 @@ impl Application for App { self.must_save_sort_names = false; if let Some(state_handler) = self.state_handler.as_ref() { if let Err(err) = state_handler - .set::>( + .set::<&FxOrderMap>( "sort_names", - self.state.sort_names.clone(), + &self.state.sort_names, ) { log::warn!("Failed to save sort names: {err:?}"); @@ -4795,7 +4726,7 @@ impl Application for App { ) .title(fl!("add-network-drive")) .header(text_input) - .footer(widget::row::with_children(vec![ + .footer(widget::row::with_children([ widget::horizontal_space().into(), button.into(), ])) @@ -4895,9 +4826,9 @@ impl Application for App { widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel), ) .control( - widget::column::with_children(vec![ + widget::column::with_children([ widget::text::body(fl!("file-name")).into(), - widget::row::with_children(vec![ + widget::row::with_children([ widget::text_input("", name.as_str()) .id(self.dialog_text_input.clone()) .on_input(move |name| { @@ -4936,8 +4867,8 @@ impl Application for App { ); if *archive_type == ArchiveType::Zip { - let password_unwrapped = password.clone().unwrap_or_else(String::default); - dialog = dialog.control(widget::column::with_children(vec![ + let password_unwrapped = password.clone().unwrap_or_default(); + dialog = dialog.control(widget::column::with_children([ widget::text::body(fl!("password")).into(), widget::text_input("", password_unwrapped) .password() @@ -5023,7 +4954,7 @@ impl Application for App { auth_tx, } => { //TODO: use URI! - let mut controls = Vec::with_capacity(4); + let mut controls = widget::column::with_capacity(4); let mut id_assigned = false; if let Some(username) = &auth.username_opt { @@ -5045,7 +4976,7 @@ impl Application for App { input = input.id(self.dialog_text_input.clone()); id_assigned = true; } - controls.push(input.into()); + controls = controls.push(input); } if let Some(domain) = &auth.domain_opt { @@ -5067,7 +4998,7 @@ impl Application for App { input = input.id(self.dialog_text_input.clone()); id_assigned = true; } - controls.push(input.into()); + controls = controls.push(input); } if let Some(password) = &auth.password_opt { @@ -5089,15 +5020,15 @@ impl Application for App { if !id_assigned { input = input.id(self.dialog_text_input.clone()); } - controls.push(input.into()); + controls = controls.push(input); } if let Some(remember) = &auth.remember_opt { //TODO: what should submit do? //TODO: button for showing password - controls.push( - widget::checkbox(fl!("remember-password"), *remember) - .on_toggle(move |value| { + controls = controls.push( + widget::checkbox(fl!("remember-password"), *remember).on_toggle( + move |value| { Message::DialogUpdate(DialogPage::NetworkAuth { mounter_key: *mounter_key, uri: uri.clone(), @@ -5107,8 +5038,8 @@ impl Application for App { }, auth_tx: auth_tx.clone(), }) - }) - .into(), + }, + ), ); } @@ -5119,7 +5050,7 @@ impl Application for App { let mut widget = widget::dialog() .title(title) .body(body) - .control(widget::column::with_children(controls).spacing(space_s)) + .control(controls.spacing(space_s)) .primary_action( widget::button::suggested(fl!("connect")).on_press(Message::DialogComplete), ) @@ -5205,7 +5136,7 @@ impl Application for App { widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel), ) .control( - widget::column::with_children(vec![ + widget::column::with_children([ widget::text::body(if *dir { fl!("folder-name") } else { @@ -5244,8 +5175,8 @@ impl Application for App { let item_height = 32.0; let mut displayed_default = false; let mut last_kind = MimeAppMatch::Exact; - for (i, (app, kind)) in available_apps.iter().enumerate() { - if *kind != last_kind { + for (i, &(app, kind)) in available_apps.iter().enumerate() { + if kind != last_kind { match kind { MimeAppMatch::Related => { column = column.add(widget::text::heading(fl!("related-apps"))); @@ -5255,12 +5186,12 @@ impl Application for App { } _ => {} } - last_kind = *kind; + last_kind = kind; } column = column.add( widget::mouse_area( widget::button::custom( - widget::row::with_children(vec![ + widget::row::with_children([ icon(app.icon.clone()).size(32).into(), if app.is_default && !displayed_default { displayed_default = true; @@ -5270,7 +5201,7 @@ impl Application for App { )) .into() } else { - widget::text::body(app.name.to_string()).into() + widget::text::body(app.name.clone()).into() }, widget::horizontal_space().into(), if *selected == i { @@ -5380,7 +5311,7 @@ impl Application for App { None } else { let path = parent.join(name); - if from != &path && path.exists() { + if *from != path && path.exists() { if path.is_dir() { dialog = dialog .tertiary_action(widget::text::body(fl!("folder-already-exists"))); @@ -5406,7 +5337,7 @@ impl Application for App { widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel), ) .control( - widget::column::with_children(vec![ + widget::column::with_children([ widget::text::body(if *dir { fl!("folder-name") } else { @@ -5591,8 +5522,8 @@ impl Application for App { let progress_bar = widget::progress_bar(0.0..=1.0, total_progress).height(progress_bar_height); - let container = widget::layer_container(widget::column::with_children(vec![ - widget::row::with_children(vec![ + let container = widget::layer_container(widget::column::with_children([ + widget::row::with_children([ progress_bar.into(), if all_paused { widget::tooltip( @@ -5626,7 +5557,7 @@ impl Application for App { .into(), widget::text::body(title).into(), widget::Space::with_height(space_s).into(), - widget::row::with_children(vec![ + widget::row::with_children([ widget::button::link(fl!("details")) .on_press(Message::ToggleContextPage(ContextPage::EditHistory)) .padding(0) @@ -5714,7 +5645,7 @@ impl Application for App { } } - if self.tab_model.iter().count() > 1 { + if self.tab_model.len() > 1 { tab_column = tab_column.push( widget::container( widget::tab_bar::horizontal(&self.tab_model) @@ -5787,14 +5718,14 @@ impl Application for App { tab_column.push(widget::toaster(&self.toasts, widget::horizontal_space())); return if let Some(margin) = self.margin.get(&id) { if margin.0 >= 0. || margin.2 >= 0. { - tab_column = widget::column::with_children(vec![ + tab_column = widget::column::with_children([ vertical_space().height(margin.0).into(), tab_column.into(), vertical_space().height(margin.2).into(), ]); } if margin.1 >= 0. || margin.3 >= 0. { - Element::from(widget::row::with_children(vec![ + Element::from(widget::row::with_children([ horizontal_space().width(margin.1).into(), tab_column.into(), horizontal_space().width(margin.3).into(), @@ -6140,24 +6071,21 @@ impl Application for App { ); } - for (key, mounter) in MOUNTERS.iter() { - subscriptions.push( - mounter.subscription().with(*key).map( - |(key, mounter_message)| match mounter_message { - MounterMessage::Items(items) => Message::MounterItems(key, items), - MounterMessage::MountResult(item, res) => { - Message::MountResult(key, item, res) - } - MounterMessage::NetworkAuth(uri, auth, auth_tx) => { - Message::NetworkAuth(key, uri, auth, auth_tx) - } - MounterMessage::NetworkResult(uri, res) => { - Message::NetworkResult(key, uri, res) - } - }, - ), - ); - } + subscriptions.extend(MOUNTERS.iter().map(|(key, mounter)| { + mounter + .subscription() + .with(*key) + .map(|(key, mounter_message)| match mounter_message { + MounterMessage::Items(items) => Message::MounterItems(key, items), + MounterMessage::MountResult(item, res) => Message::MountResult(key, item, res), + MounterMessage::NetworkAuth(uri, auth, auth_tx) => { + Message::NetworkAuth(key, uri, auth, auth_tx) + } + MounterMessage::NetworkResult(uri, res) => { + Message::NetworkResult(key, uri, res) + } + }) + })); if !self.pending_operations.is_empty() { //TODO: inhibit suspend/shutdown? @@ -6221,15 +6149,14 @@ impl Application for App { selected_preview = Some(entity_opt.unwrap_or_else(|| self.tab_model.active())); } } - for entity in self.tab_model.iter() { - if let Some(tab) = self.tab_model.data::(entity) { - subscriptions.push( - tab.subscription(selected_preview == Some(entity)) - .with(entity) - .map(|(entity, tab_msg)| Message::TabMessage(Some(entity), tab_msg)), - ); - } - } + subscriptions.extend(self.tab_model.iter().filter_map(|entity| { + let tab = self.tab_model.data::(entity)?; + Some( + tab.subscription(selected_preview == Some(entity)) + .with(entity) + .map(|(entity, tab_msg)| Message::TabMessage(Some(entity), tab_msg)), + ) + })); Subscription::batch(subscriptions) } @@ -6285,7 +6212,8 @@ pub(crate) mod test_utils { // Random alphanumeric String of length `len` fn rand_string(len: usize) -> String { - (0..len).map(|_| fastrand::alphanumeric()).collect() + let mut rng = fastrand::Rng::new(); + iter::repeat_with(|| rng.alphanumeric()).take(len).collect() } /// Create a small, temporary file hierarchy. @@ -6313,14 +6241,21 @@ pub(crate) mod test_utils { ); // All paths for directories and nested directories - let paths = (0..dirs).flat_map(|_| { + let paths = iter::repeat_with(|| { let root = root.as_ref(); let current = rand_string(name_len); iter::once(root.join(¤t)).chain( - (0..nested).map(move |_| root.join(format!("{current}/{}", rand_string(name_len)))), + iter::repeat_with(move || { + let mut path = root.join(¤t); + path.push(rand_string(name_len)); + path + }) + .take(nested), ) - }); + }) + .take(dirs) + .flatten(); // Create directories from `paths` and add a few files for path in paths { @@ -6381,17 +6316,17 @@ pub(crate) mod test_utils { } /// Filter `path` for directories - pub fn filter_dirs(path: &Path) -> io::Result> { + pub fn filter_dirs(path: &Path) -> io::Result + use<>> { Ok(path.read_dir()?.filter_map(|entry| { entry.ok().and_then(|entry| { let path = entry.path(); - if path.is_dir() { Some(path) } else { None } + path.is_dir().then_some(path) }) })) } // Filter `path` for files - pub fn filter_files(path: &Path) -> io::Result> { + pub fn filter_files(path: &Path) -> io::Result + use<>> { Ok(path.read_dir()?.filter_map(|entry| { entry.ok().and_then(|entry| { let path = entry.path(); @@ -6491,11 +6426,10 @@ pub(crate) mod test_utils { let items_len = tab.items_opt().map(Vec::len).unwrap_or_default(); assert_eq!(entries.len(), items_len); - let empty = Vec::new(); assert!( entries .into_iter() - .zip(tab.items_opt().unwrap_or(&empty)) + .zip(tab.items_opt().map_or([].as_slice(), Vec::as_slice)) .all(|(a, b)| eq_path_item(&a, b)), "Path ({}) and Tab path ({}) don't have equal contents", path.display(), diff --git a/src/archive.rs b/src/archive.rs index 75af4cf..5f0fe63 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -47,7 +47,7 @@ pub fn extract( controller: &Controller, ) -> Result<(), OperationError> { let mime = mime_for_path(path, None, false); - let password = password.clone(); + let password = password.as_deref(); match mime.essence_str() { "application/gzip" | "application/x-compressed-tar" => { OpReader::new(path, controller.clone()) @@ -107,7 +107,7 @@ pub fn extract( fn zip_extract>( archive: &mut zip::ZipArchive, directory: P, - password: Option, + password: Option<&str>, controller: Controller, ) -> zip::result::ZipResult<()> { use std::{ffi::OsString, fs}; @@ -145,7 +145,7 @@ fn zip_extract>( controller.set_progress((i as f32) / total_files as f32); - let mut file = match &password { + let mut file = match password { None => archive.by_index(i), Some(pwd) => archive.by_index_decrypt(i, pwd.as_bytes()), }?; @@ -207,7 +207,7 @@ fn zip_extract>( } continue; } - let mut file = match &password { + let mut file = match password { None => archive.by_index(i), Some(pwd) => archive.by_index_decrypt(i, pwd.as_bytes()), }?; diff --git a/src/clipboard.rs b/src/clipboard.rs index b0144b9..0c17a09 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -25,7 +25,7 @@ pub struct ClipboardCopy { } impl ClipboardCopy { - pub fn new>(kind: ClipboardKind, paths: &[P]) -> Self { + pub fn new>(kind: ClipboardKind, paths: impl IntoIterator) -> Self { let available = vec![ "text/plain".to_string(), "text/plain;charset=utf-8".to_string(), diff --git a/src/config.rs b/src/config.rs index 8689121..d537a3f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -75,21 +75,17 @@ pub enum Favorite { impl Favorite { pub fn from_path(path: PathBuf) -> Self { // Ensure that special folders are handled properly - for favorite in &[ + [ Self::Home, Self::Documents, Self::Downloads, Self::Music, Self::Pictures, Self::Videos, - ] { - if let Some(favorite_path) = favorite.path_opt() { - if favorite_path == path { - return favorite.clone(); - } - } - } - Self::Path(path) + ] + .into_iter() + .find(|fav| fav.path_opt().as_ref() == Some(&path)) + .unwrap_or(Self::Path(path)) } pub fn path_opt(&self) -> Option { diff --git a/src/dialog.rs b/src/dialog.rs index 0a4f063..44f3a45 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -31,9 +31,7 @@ use std::{ any::TypeId, collections::{HashMap, VecDeque}, env, fmt, fs, - num::NonZeroU16, path::PathBuf, - str::FromStr, time::{self, Instant}, }; @@ -48,6 +46,7 @@ use crate::{ menu, mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage}, tab::{self, ItemMetadata, Location, Tab}, + zoom::{zoom_in_view, zoom_out_view, zoom_to_default}, }; #[derive(Clone, Debug)] @@ -372,7 +371,7 @@ impl Dialog { let on_result_message = (self.on_result)(result); Task::batch([ command, - Task::perform(async move { cosmic::action::app(on_result_message) }, |x| x), + Task::future(async move { cosmic::action::app(on_result_message) }), ]) } else { command @@ -606,7 +605,7 @@ impl App { row = row.push( //TODO: easier way to create buttons with rich text widget::button::custom( - widget::row::with_children(vec![Element::from(&self.accept_label)]) + widget::row::with_children([Element::from(&self.accept_label)]) .padding([0, space_s]) .width(Length::Shrink) .height(space_l) @@ -677,40 +676,37 @@ impl App { let location = self.tab.location.clone(); let icon_sizes = self.tab.config.icon_sizes; let mounter_items = self.mounter_items.clone(); - Task::perform( - async move { - let location2 = location.clone(); - match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { - Ok((parent_item_opt, mut items)) => { - #[cfg(feature = "gvfs")] - { - let mounter_paths: Vec<_> = mounter_items - .iter() - .flat_map(|item| item.1.iter()) - .filter_map(MounterItem::path) - .collect(); - if !mounter_paths.is_empty() { - for item in &mut items { - item.is_mount_point = - item.path_opt().is_some_and(|p| mounter_paths.contains(p)); - } + Task::future(async move { + let location2 = location.clone(); + match tokio::task::spawn_blocking(move || location2.scan(icon_sizes)).await { + Ok((parent_item_opt, mut items)) => { + #[cfg(feature = "gvfs")] + { + let mounter_paths: Box<[_]> = mounter_items + .values() + .flatten() + .filter_map(MounterItem::path) + .collect(); + if !mounter_paths.is_empty() { + for item in &mut items { + item.is_mount_point = + item.path_opt().is_some_and(|p| mounter_paths.contains(p)); } } - cosmic::action::app(Message::TabRescan( - location, - parent_item_opt, - items, - selection_paths, - )) - } - Err(err) => { - log::warn!("failed to rescan: {err}"); - cosmic::action::none() } + cosmic::action::app(Message::TabRescan( + location, + parent_item_opt, + items, + selection_paths, + )) } - }, - |x| x, - ) + Err(err) => { + log::warn!("failed to rescan: {err}"); + cosmic::action::none() + } + } + }) } fn search_get(&self) -> Option<&str> { @@ -835,12 +831,10 @@ impl App { // Collect all mounter items let mut nav_items = Vec::new(); for (key, items) in &self.mounter_items { - for item in items { - nav_items.push((*key, item)); - } + nav_items.extend(items.iter().map(|item| (*key, item))); } // Sort by name lexically - nav_items.sort_by(|a, b| LANGUAGE_SORTER.compare(&a.1.name(), &b.1.name())); + nav_items.sort_unstable_by(|a, b| LANGUAGE_SORTER.compare(&a.1.name(), &b.1.name())); // Add items to nav model for (i, (key, item)) in nav_items.into_iter().enumerate() { nav_model = nav_model.insert(|mut b| { @@ -1040,7 +1034,7 @@ impl Application for App { //TODO: should gallery view just be a dialog? if self.tab.gallery { return Some( - widget::column::with_children(vec![ + widget::column::with_children([ self.tab.gallery_view().map(Message::TabMessage), // Draw button row as part of the overlay widget::container(self.button_view()) @@ -1098,7 +1092,7 @@ impl Application for App { widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel), ) .control( - widget::column::with_children(vec![ + widget::column::with_children([ widget::text::body(fl!("folder-name")).into(), widget::text_input("", name.as_str()) .id(self.dialog_text_input.clone()) @@ -1392,10 +1386,9 @@ impl Application for App { return self.search_set(Some(term)); } TypeToSearch::EnterPath => { - let location = self.tab.edit_location.as_ref().map_or_else( - || self.tab.location.clone(), - |x| x.location.clone(), - ); + let location = (self.tab.edit_location) + .as_ref() + .map_or_else(|| &self.tab.location, |x| &x.location); // 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(); @@ -1538,7 +1531,7 @@ impl Application for App { if let Some(path) = item.path_opt() { paths.push(path.clone()); let _ = update_recently_used( - &path.clone(), + path, Self::APP_ID.to_string(), "cosmic-files".to_string(), None, @@ -1775,9 +1768,9 @@ impl Application for App { // Filter if let Some(filter_i) = self.filter_selected { if let Some(filter) = self.filters.get(filter_i) { - // Parse filters + // Parse globs (Mime implements PartialEq with &str, so no need to parse) let mut parsed_globs = Vec::new(); - let mut parsed_mimes = Vec::new(); + let mut mimes = Vec::new(); for pattern in &filter.patterns { match pattern { DialogFilterPattern::Glob(value) => { @@ -1788,39 +1781,17 @@ impl Application for App { } } } - DialogFilterPattern::Mime(value) => { - match mime_guess::Mime::from_str(value) { - Ok(mime) => parsed_mimes.push(mime), - Err(err) => { - log::warn!("failed to parse mime {value:?}: {err}"); - } - } - } + DialogFilterPattern::Mime(value) => mimes.push(value.as_str()), } } items.retain(|item| { - if item.metadata.is_dir() { - // Directories are always shown - return true; - } - + // Directories are always shown + item.metadata.is_dir() // Check for mime type match (first because it is faster) - for mime in &parsed_mimes { - if mime == &item.mime { - return true; - } - } - + || mimes.iter().copied().any(|mime| mime == item.mime) // Check for glob match (last because it is slower) - for glob in &parsed_globs { - if glob.matches(&item.name) { - return true; - } - } - - // No filters matched - false + || parsed_globs.iter().any(|glob| glob.matches(&item.name)) }); } } @@ -1869,47 +1840,18 @@ impl Application for App { }); } Message::ZoomDefault => { - return self.with_dialog_config(|config| match config.view { - tab::View::List => config.icon_sizes.list = 100.try_into().unwrap(), - tab::View::Grid => config.icon_sizes.grid = 100.try_into().unwrap(), + return self.with_dialog_config(|config| { + zoom_to_default(config.view, &mut config.icon_sizes); }); } Message::ZoomIn => { - let zoom_in = |size: &mut NonZeroU16, min: u16, max: u16| { - let mut step = min; - while step <= max { - if size.get() < step { - *size = step.try_into().unwrap(); - break; - } - step += 25; - } - if size.get() > step { - *size = step.try_into().unwrap(); - } - }; - return self.with_dialog_config(|config| match config.view { - tab::View::List => zoom_in(&mut config.icon_sizes.list, 50, 500), - tab::View::Grid => zoom_in(&mut config.icon_sizes.grid, 50, 500), + return self.with_dialog_config(|config| { + zoom_in_view(config.view, &mut config.icon_sizes); }); } Message::ZoomOut => { - let zoom_out = |size: &mut NonZeroU16, min: u16, max: u16| { - let mut step = max; - while step >= min { - if size.get() > step { - *size = step.try_into().unwrap(); - break; - } - step -= 25; - } - if size.get() < step { - *size = step.try_into().unwrap(); - } - }; - return self.with_dialog_config(|config| match config.view { - tab::View::List => zoom_out(&mut config.icon_sizes.list, 50, 500), - tab::View::Grid => zoom_out(&mut config.icon_sizes.grid, 50, 500), + return self.with_dialog_config(|config| { + zoom_out_view(config.view, &mut config.icon_sizes); }); } Message::Surface(action) => { @@ -2090,21 +2032,19 @@ impl Application for App { ); } - for (key, mounter) in MOUNTERS.iter() { - subscriptions.push( - mounter - .subscription() - .with(*key) - .map(|(key, mounter_message)| { - if let MounterMessage::Items(items) = mounter_message { - Message::MounterItems(key, items) - } else { - log::warn!("{mounter_message:?} not supported in dialog mode"); - Message::None - } - }), - ); - } + subscriptions.extend(MOUNTERS.iter().map(|(key, mounter)| { + mounter + .subscription() + .with(*key) + .map(|(key, mounter_message)| { + if let MounterMessage::Items(items) = mounter_message { + Message::MounterItems(key, items) + } else { + log::warn!("{mounter_message:?} not supported in dialog mode"); + Message::None + } + }) + })); Subscription::batch(subscriptions) } diff --git a/src/lib.rs b/src/lib.rs index 9b18488..6ffbe2e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ mod mouse_area; pub mod operation; mod spawn_detached; use tab::Location; +mod zoom; use crate::config::State; pub mod tab; diff --git a/src/menu.rs b/src/menu.rs index db16472..6af7687 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -32,7 +32,7 @@ macro_rules! menu_button { ($($x:expr),+ $(,)?) => ( button::custom( Row::with_children( - vec![$(Element::from($x)),+] + [$(Element::from($x)),+] ) .height(Length::Fixed(24.0)) .align_y(Alignment::Center) @@ -167,9 +167,9 @@ pub fn context_menu<'a>( children.push(menu_item(fl!("open"), Action::Open).into()); #[cfg(feature = "desktop")] { - for (i, action) in entry.desktop_actions.into_iter().enumerate() { - children.push(menu_item(action.name, Action::ExecEntryAction(i)).into()); - } + children.extend(entry.desktop_actions.into_iter().enumerate().map( + |(i, action)| menu_item(action.name, Action::ExecEntryAction(i)).into(), + )); } children.push(divider::horizontal::light().into()); children.push(menu_item(fl!("rename"), Action::Rename).into()); @@ -207,11 +207,8 @@ pub fn context_menu<'a>( children.push(menu_item(fl!("copy"), Action::Copy).into()); children.push(divider::horizontal::light().into()); - let supported_archive_types = crate::archive::SUPPORTED_ARCHIVE_TYPES - .iter() - .filter_map(|mime_type| mime_type.parse::().ok()) - .collect::>(); - selected_types.retain(|t| !supported_archive_types.contains(t)); + let supported_archive_types = crate::archive::SUPPORTED_ARCHIVE_TYPES; + selected_types.retain(|t| supported_archive_types.iter().copied().all(|m| *t != m)); if selected_types.is_empty() { children.push(menu_item(fl!("extract-here"), Action::ExtractHere).into()); children.push(menu_item(fl!("extract-to"), Action::ExtractTo).into()); @@ -719,7 +716,7 @@ pub fn menu_bar<'a>( pub fn location_context_menu<'a>(ancestor_index: usize) -> Element<'a, tab::Message> { //TODO: only add some of these when in App mode - let children = vec![ + let children = [ menu_button!(text::body(fl!("open-in-new-tab"))) .on_press(tab::Message::LocationMenuAction( LocationMenuAction::OpenInNewTab(ancestor_index), diff --git a/src/mime_app.rs b/src/mime_app.rs index fba710b..1ad8643 100644 --- a/src/mime_app.rs +++ b/src/mime_app.rs @@ -83,7 +83,7 @@ pub fn exec_to_command( for invalid in path_opt .iter() .map(AsRef::as_ref) - .filter(|path| from_file_or_dir(path).is_none()) + .filter(|&path| from_file_or_dir(path).is_none()) { log::warn!("Desktop file expects a file path instead of a URL: {invalid:?}"); } @@ -221,7 +221,7 @@ fn filename_eq(path_opt: &Option, filename: &str) -> bool { pub struct MimeAppCache { apps: Vec, cache: FxHashMap>, - icons: FxHashMap>, + icons: FxHashMap>, terminals: Vec, } @@ -257,7 +257,7 @@ impl MimeAppCache { // Load desktop applications by supported mime types //TODO: hashmap for all apps by id? - let all_apps: Vec<_> = desktop::load_applications(locale, false, None).collect(); + let all_apps: Box<[_]> = desktop::load_applications(locale, false, None).collect(); for app in &all_apps { //TODO: just collect apps that can be executed with a file argument? if !app.mime_types.is_empty() { @@ -292,21 +292,17 @@ impl MimeAppCache { let mut mimeapps_paths = Vec::new(); let xdg_dirs = xdg::BaseDirectories::new(); - for path in xdg_dirs.find_data_files("applications/mimeapps.list") { - mimeapps_paths.push(path); - } + mimeapps_paths.extend(xdg_dirs.find_data_files("applications/mimeapps.list")); + for desktop in desktops.iter().rev() { - for path in xdg_dirs.find_data_files(format!("applications/{desktop}-mimeapps.list")) { - mimeapps_paths.push(path); - } - } - for path in xdg_dirs.find_config_files("mimeapps.list") { - mimeapps_paths.push(path); + mimeapps_paths + .extend(xdg_dirs.find_data_files(format!("applications/{desktop}-mimeapps.list"))); } + + mimeapps_paths.extend(xdg_dirs.find_config_files("mimeapps.list")); + for desktop in desktops.iter().rev() { - for path in xdg_dirs.find_config_files(format!("{desktop}-mimeapps.list")) { - mimeapps_paths.push(path); - } + mimeapps_paths.extend(xdg_dirs.find_config_files(format!("{desktop}-mimeapps.list"))); } //TODO: handle directory specific behavior @@ -334,7 +330,7 @@ impl MimeAppCache { .or_insert_with(|| Vec::with_capacity(1)); if !apps.iter().any(|x| filename_eq(&x.path, filename)) { if let Some(app) = - all_apps.iter().find(|x| filename_eq(&x.path, filename)) + all_apps.iter().find(|&x| filename_eq(&x.path, filename)) { apps.push(MimeApp::from(app)); } else { @@ -406,12 +402,12 @@ impl MimeAppCache { // Copy icons to special cache //TODO: adjust dropdown API so this is no longer needed - for (mime, apps) in &self.cache { - self.icons.insert( + self.icons.extend(self.cache.iter().map(|(mime, apps)| { + ( mime.clone(), apps.iter().map(|app| app.icon.clone()).collect(), - ); - } + ) + })); let elapsed = start.elapsed(); log::info!("loaded mime app cache in {elapsed:?}"); @@ -426,7 +422,7 @@ impl MimeAppCache { } pub fn icons(&self, key: &Mime) -> &[widget::icon::Handle] { - self.icons.get(key).map_or(&[], Vec::as_slice) + self.icons.get(key).map_or(&[], Box::as_ref) } fn get_default_terminal(&self) -> Option { diff --git a/src/mime_icon.rs b/src/mime_icon.rs index 73674ee..52b1a27 100644 --- a/src/mime_icon.rs +++ b/src/mime_icon.rs @@ -41,10 +41,8 @@ impl MimeIconCache { let icon_name = icon_names.remove(0); let mut named = icon::from_name(icon_name).size(key.size); if !icon_names.is_empty() { - let mut fallback_names = Vec::with_capacity(icon_names.len()); - for fallback_name in icon_names { - fallback_names.push(fallback_name.into()); - } + let fallback_names = + icon_names.into_iter().map(std::borrow::Cow::from).collect(); named = named.fallback(Some(icon::IconFallback::Names(fallback_names))); } Some(named.handle()) @@ -55,8 +53,8 @@ impl MimeIconCache { static MIME_ICON_CACHE: LazyLock> = LazyLock::new(|| Mutex::new(MimeIconCache::new())); -pub fn mime_for_path>( - path: P, +pub fn mime_for_path( + path: impl AsRef, metadata_opt: Option<&fs::Metadata>, remote: bool, ) -> Mime { @@ -65,7 +63,7 @@ pub fn mime_for_path>( // Try the shared mime info cache first let mut gb = mime_icon_cache.shared_mime_info.guess_mime_type(); if remote { - if let Some(file_name) = path.file_name().and_then(|x| x.to_str()) { + if let Some(file_name) = path.file_name().and_then(std::ffi::OsStr::to_str) { gb.file_name(file_name); } } else { @@ -75,11 +73,22 @@ pub fn mime_for_path>( gb.metadata(metadata.clone()); } let guess = gb.guess(); - if guess.uncertain() { + let guessed_mime = guess.mime_type(); + + /// Checks if the `Mime` is a special variant returned by `xdg-mime`. + /// This includes directories, symlinks and zerosize files, which are returned as uncertain. + fn is_special_mime(mime: &Mime) -> bool { + *mime == "inode/directory" || *mime == "inode/symlink" || *mime == "application/x-zerosize" + } + + // `xdg-mime-rs` sets the guess to uncertain if it returns special mime types. + // The guess could also be uncertain on platforms without shared-mime-info. + // Try mime_guess, but only if it is not one of the special mime types. + if guess.uncertain() && (remote || !is_special_mime(guessed_mime)) { // If uncertain, try mime_guess. This could happen on platforms without shared-mime-info mime_guess::from_path(path).first_or_octet_stream() } else { - guess.mime_type().clone() + guessed_mime.clone() } } diff --git a/src/mounter/gvfs.rs b/src/mounter/gvfs.rs index 7c84039..4d2a44d 100644 --- a/src/mounter/gvfs.rs +++ b/src/mounter/gvfs.rs @@ -30,45 +30,48 @@ fn gio_icon_to_path(icon: &gio::Icon, size: u16) -> Option { } fn items(monitor: &gio::VolumeMonitor, sizes: IconSizes) -> MounterItems { - let mut items = MounterItems::new(); - for (i, mount) in monitor.mounts().into_iter().enumerate() { - items.push(MounterItem::Gvfs(Item { - uri: MountExt::root(&mount).uri().to_string(), - kind: ItemKind::Mount, - index: i, - name: MountExt::name(&mount).to_string(), - is_mounted: true, - icon_opt: gio_icon_to_path(&MountExt::icon(&mount), sizes.grid()), - icon_symbolic_opt: gio_icon_to_path(&MountExt::symbolic_icon(&mount), 16), - path_opt: MountExt::root(&mount).path(), - })); - } - for (i, volume) in monitor.volumes().into_iter().enumerate() { - if volume.get_mount().is_some() { + let mut items: MounterItems = (monitor.mounts().into_iter()) + .enumerate() + .map(|(i, mount)| { + MounterItem::Gvfs(Item { + uri: mount.root().uri().into(), + kind: ItemKind::Mount, + index: i, + name: mount.name().into(), + is_mounted: true, + icon_opt: gio_icon_to_path(&MountExt::icon(&mount), sizes.grid()), + icon_symbolic_opt: gio_icon_to_path(&MountExt::symbolic_icon(&mount), 16), + path_opt: MountExt::root(&mount).path(), + }) + }) + .collect(); + items.extend( + (monitor.volumes().into_iter()) + .enumerate() // Volumes with mounts are already listed by mount - continue; - } - let uri = VolumeExt::activation_root(&volume) - .map(|f| f.uri().to_string()) - .unwrap_or_default(); - items.push(MounterItem::Gvfs(Item { - // TODO can we get URI for volumes with no mount? - uri, - kind: ItemKind::Volume, - index: i, - name: VolumeExt::name(&volume).to_string(), - is_mounted: false, - icon_opt: gio_icon_to_path(&VolumeExt::icon(&volume), sizes.grid()), - icon_symbolic_opt: gio_icon_to_path(&VolumeExt::symbolic_icon(&volume), 16), - path_opt: None, - })); - } + .filter(|(_, volume)| volume.get_mount().is_none()) + .map(|(i, volume)| { + let uri = VolumeExt::activation_root(&volume) + .map(|f| f.uri().into()) + .unwrap_or_default(); + MounterItem::Gvfs(Item { + // TODO can we get URI for volumes with no mount? + uri, + kind: ItemKind::Volume, + index: i, + name: volume.name().into(), + is_mounted: false, + icon_opt: gio_icon_to_path(&VolumeExt::icon(&volume), sizes.grid()), + icon_symbolic_opt: gio_icon_to_path(&VolumeExt::symbolic_icon(&volume), 16), + path_opt: None, + }) + }), + ); items } fn network_scan(uri: &str, sizes: IconSizes) -> Result, String> { - let mut uri = uri.to_string(); - let mut file = gio::File::for_uri(&uri); + let mut file = gio::File::for_uri(uri); let force_dir = uri.starts_with("network:///"); // Resolve the target-uri if it exists @@ -78,8 +81,7 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result, String> { gio::Cancellable::NONE, ) { if let Some(resolved_uri) = file_info.attribute_as_string(TARGET_URI_ATTRIBUTE) { - uri = resolved_uri.to_string(); - file = gio::File::for_uri(&uri); + file = gio::File::for_uri(resolved_uri.as_str()); } } @@ -89,10 +91,10 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result, String> { .map_err(err_str)? { let info = info_res.map_err(err_str)?; - let name = info.name().to_string_lossy().to_string(); - let display_name = info.display_name().to_string(); + let name = info.name().to_string_lossy().into_owned(); + let display_name = String::from(info.display_name()); - let uri = file.child(info.name()).uri().to_string(); + let uri = String::from(file.child(info.name()).uri()); //TODO: what is the best way to resolve shortcuts? let location = Location::Network(uri, display_name.clone(), file.child(&name).path()); @@ -100,11 +102,7 @@ fn network_scan(uri: &str, sizes: IconSizes) -> Result, String> { let metadata = if !force_dir && !info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE) { let mtime = info.attribute_uint64(gio::FILE_ATTRIBUTE_TIME_MODIFIED); let is_dir = matches!(info.file_type(), gio::FileType::Directory); - let size_opt = if is_dir { - None - } else { - Some(info.size() as u64) - }; + let size_opt = (!is_dir).then_some(info.size() as u64); let mut children_opt = None; if is_dir { @@ -189,31 +187,21 @@ fn mount_op(uri: String, event_tx: mpsc::UnboundedSender) -> gio::MountOp move |mount_op, message, default_user, default_domain, flags| { let auth = MounterAuth { message: message.to_string(), - username_opt: if flags.contains(gio::AskPasswordFlags::NEED_USERNAME) { - Some(default_user.to_string()) - } else { - None - }, - domain_opt: if flags.contains(gio::AskPasswordFlags::NEED_DOMAIN) { - Some(default_domain.to_string()) - } else { - None - }, - password_opt: if flags.contains(gio::AskPasswordFlags::NEED_PASSWORD) { - Some(String::new()) - } else { - None - }, - remember_opt: if flags.contains(gio::AskPasswordFlags::SAVING_SUPPORTED) { - Some(false) - } else { - None - }, - anonymous_opt: if flags.contains(gio::AskPasswordFlags::ANONYMOUS_SUPPORTED) { - Some(false) - } else { - None - }, + username_opt: flags + .contains(gio::AskPasswordFlags::NEED_USERNAME) + .then(|| default_user.to_string()), + domain_opt: flags + .contains(gio::AskPasswordFlags::NEED_DOMAIN) + .then(|| default_domain.to_string()), + password_opt: flags + .contains(gio::AskPasswordFlags::NEED_PASSWORD) + .then(String::new), + remember_opt: flags + .contains(gio::AskPasswordFlags::SAVING_SUPPORTED) + .then_some(false), + anonymous_opt: flags + .contains(gio::AskPasswordFlags::ANONYMOUS_SUPPORTED) + .then_some(false), }; let (auth_tx, mut auth_rx) = mpsc::channel(1); event_tx @@ -458,7 +446,7 @@ impl Gvfs { gio::Cancellable::NONE, ) { if let Some(resolved_uri) = file_info.attribute_as_string(TARGET_URI_ATTRIBUTE) { - uri = resolved_uri.to_string(); + uri = resolved_uri.into(); file = gio::File::for_uri(&uri); } } @@ -597,12 +585,9 @@ impl Mounter for Gvfs { fn unmount(&self, item: MounterItem) -> Task<()> { let command_tx = self.command_tx.clone(); - Task::perform( - async move { - command_tx.send(Cmd::Unmount(item)).unwrap(); - }, - |x| x, - ) + Task::future(async move { + command_tx.send(Cmd::Unmount(item)).unwrap(); + }) } fn subscription(&self) -> Subscription { diff --git a/src/operation/mod.rs b/src/operation/mod.rs index 23fd18f..49dd415 100644 --- a/src/operation/mod.rs +++ b/src/operation/mod.rs @@ -220,8 +220,9 @@ fn copy_unique_path(from: &Path, to: &Path) -> PathBuf { let file_name = file_name.to_string(); COMPOUND_EXTENSIONS .iter() - .find(|&&ext| file_name.ends_with(ext)) - .map(|&ext| { + .copied() + .find(|&ext| file_name.ends_with(ext)) + .map(|ext| { ( file_name.strip_suffix(ext).unwrap().to_string(), Some(ext[1..].to_string()), @@ -251,7 +252,7 @@ fn copy_unique_path(from: &Path, to: &Path) -> PathBuf { } }; - to = to.join(&new_name); + to.push(&new_name); if !matches!(to.try_exists(), Ok(true)) { break; @@ -329,7 +330,7 @@ pub enum Operation { EmptyTrash, /// Uncompress files Extract { - paths: Vec, + paths: Box<[PathBuf]>, to: PathBuf, password: Option, }, @@ -347,10 +348,10 @@ pub enum Operation { }, /// Permanently delete items, skipping the trash PermanentlyDelete { - paths: Vec, + paths: Box<[PathBuf]>, }, RemoveFromRecents { - paths: Vec, + paths: Box<[PathBuf]>, }, Rename { from: PathBuf, @@ -1013,7 +1014,7 @@ impl Operation { } Self::RemoveFromRecents { paths } => { tokio::task::spawn_blocking(move || { - let path_refs = paths.iter().map(PathBuf::as_path).collect::>(); + let path_refs = paths.iter().map(PathBuf::as_path).collect::>(); recently_used_xbel::remove_recently_used(&path_refs) }) .await diff --git a/src/operation/recursive.rs b/src/operation/recursive.rs index c6caac5..5233028 100644 --- a/src/operation/recursive.rs +++ b/src/operation/recursive.rs @@ -52,7 +52,7 @@ impl Context { pub async fn recursive_copy_or_move( &mut self, - from_to_pairs: Vec<(PathBuf, PathBuf)>, + from_to_pairs: impl IntoIterator, method: Method, ) -> Result { let mut ops = Vec::new(); @@ -148,9 +148,8 @@ impl Context { } // Add cleanup ops after standard ops, in reverse - for cleanup_op in cleanup_ops.into_iter().rev() { - ops.push(cleanup_op); - } + cleanup_ops.reverse(); + ops.append(&mut cleanup_ops); let total_ops = ops.len(); for (current_ops, mut op) in ops.into_iter().enumerate() { diff --git a/src/tab.rs b/src/tab.rs index c07a28e..4101525 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -54,7 +54,7 @@ use serde::{Deserialize, Serialize}; use std::{ borrow::Cow, cell::{Cell, RefCell}, - cmp::Ordering, + cmp::{Ordering, Reverse}, collections::HashMap, error::Error, fmt::{self, Display}, @@ -483,18 +483,11 @@ impl Display for FormatTime<'_> { }; if datetime.date_naive() == now.date_naive() { - write!( - f, - "{}, {}", - fl!("today"), - self.time_formatter.format(&icu_datetime).to_string() - ) + f.write_str(fl!("today").as_str())?; + f.write_str(", ")?; + self.time_formatter.format(&icu_datetime).fmt(f) } else { - write!( - f, - "{}", - self.date_time_formatter.format(&icu_datetime).to_string() - ) + self.date_time_formatter.format(&icu_datetime).fmt(f) } } } @@ -606,11 +599,7 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS let remote = file_info.boolean(gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE); let is_dir = matches!(file_info.file_type(), gio::FileType::Directory); - let size_opt = if is_dir { - None - } else { - Some(file_info.size() as u64) - }; + let size_opt = (!is_dir).then_some(file_info.size() as u64); let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = if is_dir { ( @@ -673,8 +662,10 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS } } + let hidden = file_name.starts_with('.'); + Item { - name: file_name.clone().to_string(), + name: file_name.into(), display_name, is_mount_point: false, metadata: ItemMetadata::GvfsPath { @@ -682,7 +673,7 @@ pub fn item_from_gvfs_info(path: PathBuf, file_info: gio::FileInfo, sizes: IconS size_opt, children_opt, }, - hidden: file_name.starts_with('.'), + hidden, location_opt: Some(Location::Path(path)), mime, icon_handle_grid, @@ -790,7 +781,7 @@ pub fn item_from_entry( widget::icon::from_name(&*icon_name) .size(sizes.list()) .handle(), - widget::icon::from_name(&*icon_name) + widget::icon::from_name(icon_name) .size(sizes.list_condensed()) .handle(), ) @@ -833,11 +824,7 @@ pub fn item_from_entry( icon_handle_grid, icon_handle_list, icon_handle_list_condensed, - thumbnail_opt: if remote { - Some(ItemThumbnail::NotImage) - } else { - None - }, + thumbnail_opt: remote.then_some(ItemThumbnail::NotImage), button_id: widget::Id::unique(), pos_opt: Cell::new(None), rect_opt: Cell::new(None), @@ -870,7 +857,7 @@ pub fn item_from_path>(path: P, sizes: IconSizes) -> Result Vec { let mut items = Vec::new(); - let mut hidden_files = Vec::new(); + let mut hidden_files = Box::from([]); let mut remote_scannable = false; #[cfg(feature = "gvfs")] @@ -880,19 +867,15 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec { let file = gio::File::for_path(tab_path); // gio crate expects a comma delimited string - let mut attr_string = String::new(); - for attr in [ - gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, - gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE, - gio::FILE_ATTRIBUTE_TIME_MODIFIED, - gio::FILE_ATTRIBUTE_STANDARD_SIZE, - gio::FILE_ATTRIBUTE_STANDARD_TYPE, - gio::FILE_ATTRIBUTE_STANDARD_NAME, - ] { - attr_string.push_str(attr); - attr_string.push(','); - } - attr_string.pop(); + let attr_string = [ + gio::FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME.as_str(), + gio::FILE_ATTRIBUTE_FILESYSTEM_REMOTE.as_str(), + gio::FILE_ATTRIBUTE_TIME_MODIFIED.as_str(), + gio::FILE_ATTRIBUTE_STANDARD_SIZE.as_str(), + gio::FILE_ATTRIBUTE_STANDARD_TYPE.as_str(), + gio::FILE_ATTRIBUTE_STANDARD_NAME.as_str(), + ] + .join(","); match gio::prelude::FileExt::enumerate_children( &file, @@ -902,10 +885,12 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec { ) { Ok(res) => { remote_scannable = true; - for file in res.flatten() { - let full_path = Path::new(tab_path).join(file.name()); - items.push(item_from_gvfs_info(full_path, file, sizes)); - } + items = res + .filter_map(|file| { + let file = file.ok()?; + Some(item_from_gvfs_info(tab_path.join(file.name()), file, sizes)) + }) + .collect(); } Err(err) => { log::warn!( @@ -922,60 +907,62 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec { if !remote_scannable { match fs::read_dir(tab_path) { Ok(entries) => { - for entry_res in entries { - let entry = match entry_res { - Ok(ok) => ok, - Err(err) => { - log::warn!("failed to read entry in {}: {}", tab_path.display(), err); - continue; + items = entries + .filter_map(|entry_res| { + let entry = entry_res + .inspect_err(|err| { + log::warn!( + "failed to read entry in {}: {}", + tab_path.display(), + err + ) + }) + .ok()?; + + let path = entry.path(); + + let name = entry + .file_name() + .into_string() + .inspect_err(|name_os| { + log::warn!( + "failed to parse entry at {}: {:?} is not valid UTF-8", + path.display(), + name_os + ) + }) + .ok()?; + + if name == ".hidden" && path.is_file() { + hidden_files = parse_hidden_file(&path); } - }; - let path = entry.path(); + let metadata = fs::metadata(&path) + .inspect_err(|err| { + log::warn!( + "failed to read metadata for entry at {}: {}", + path.display(), + err + ) + }) + .ok()?; - let name = match entry.file_name().into_string() { - Ok(ok) => ok, - Err(name_os) => { - log::warn!( - "failed to parse entry at {}: {:?} is not valid UTF-8", - path.display(), - name_os - ); - continue; - } - }; - - if name == ".hidden" && path.is_file() { - hidden_files = parse_hidden_file(&path); - } - - let metadata = match fs::metadata(&path) { - Ok(ok) => ok, - Err(err) => { - log::warn!( - "failed to read metadata for entry at {}: {}", - path.display(), - err - ); - continue; - } - }; - - items.push(item_from_entry(path, name, metadata, sizes)); - } + Some(item_from_entry(path, name, metadata, sizes)) + }) + .collect(); } Err(err) => { log::warn!("failed to read directory {}: {}", tab_path.display(), err); } } } - items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) { + items.sort_unstable_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) { (true, false) => Ordering::Less, (false, true) => Ordering::Greater, _ => LANGUAGE_SORTER.compare(&a.display_name, &b.display_name), }); for item in &mut items { - if hidden_files.iter().any(|hidden| &item.name == hidden) { + if hidden_files.contains(&item.name) { item.hidden = true; } } @@ -1074,70 +1061,69 @@ pub fn scan_trash(_sizes: IconSizes) -> Vec { ) ))] pub fn scan_trash(sizes: IconSizes) -> Vec { - let mut items: Vec = Vec::new(); - match trash::os_limited::list() { - Ok(entries) => { - for entry in entries { - let metadata = match trash::os_limited::metadata(&entry) { - Ok(ok) => ok, - Err(err) => { - log::warn!("failed to get metadata for trash item {entry:?}: {err}"); - continue; + let entries = match trash::os_limited::list() { + Ok(entry) => entry, + Err(err) => { + log::warn!("failed to read trash items: {err}"); + return Vec::new(); + } + }; + let mut items: Vec<_> = entries + .into_iter() + .filter_map(|entry| { + let metadata = trash::os_limited::metadata(&entry) + .inspect_err(|err| { + log::warn!("failed to get metadata for trash item {entry:?}: {err}") + }) + .ok()?; + let original_path = entry.original_path(); + let name = entry.name.to_string_lossy().into_owned(); + let display_name = Item::display_name(&name); + + let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = + match metadata.size { + trash::TrashItemSize::Entries(_) => ( + //TODO: make this a static + "inode/directory".parse().unwrap(), + folder_icon(&original_path, sizes.grid()), + folder_icon(&original_path, sizes.list()), + folder_icon(&original_path, sizes.list_condensed()), + ), + trash::TrashItemSize::Bytes(_) => { + // This passes remote = true so it does not read from the original path + let mime = mime_for_path(&original_path, None, true); + ( + mime.clone(), + mime_icon(mime.clone(), sizes.grid()), + mime_icon(mime.clone(), sizes.list()), + mime_icon(mime, sizes.list_condensed()), + ) } }; - let original_path = entry.original_path(); - let name = entry.name.to_string_lossy().to_string(); - let display_name = Item::display_name(&name); - - let (mime, icon_handle_grid, icon_handle_list, icon_handle_list_condensed) = - match metadata.size { - trash::TrashItemSize::Entries(_) => ( - //TODO: make this a static - "inode/directory".parse().unwrap(), - folder_icon(&original_path, sizes.grid()), - folder_icon(&original_path, sizes.list()), - folder_icon(&original_path, sizes.list_condensed()), - ), - trash::TrashItemSize::Bytes(_) => { - // This passes remote = true so it does not read from the original path - let mime = mime_for_path(&original_path, None, true); - ( - mime.clone(), - mime_icon(mime.clone(), sizes.grid()), - mime_icon(mime.clone(), sizes.list()), - mime_icon(mime, sizes.list_condensed()), - ) - } - }; - - items.push(Item { - name, - display_name, - is_mount_point: false, - metadata: ItemMetadata::Trash { metadata, entry }, - hidden: false, - location_opt: None, - mime, - icon_handle_grid, - icon_handle_list, - icon_handle_list_condensed, - thumbnail_opt: Some(ItemThumbnail::NotImage), - button_id: widget::Id::unique(), - pos_opt: Cell::new(None), - rect_opt: Cell::new(None), - selected: false, - highlighted: false, - overlaps_drag_rect: false, - dir_size: DirSize::NotDirectory, - cut: false, - }); - } - } - Err(err) => { - log::warn!("failed to read trash items: {err}"); - } - } + Some(Item { + name, + display_name, + is_mount_point: false, + metadata: ItemMetadata::Trash { metadata, entry }, + hidden: false, + location_opt: None, + mime, + icon_handle_grid, + icon_handle_list, + icon_handle_list_condensed, + thumbnail_opt: Some(ItemThumbnail::NotImage), + button_id: widget::Id::unique(), + pos_opt: Cell::new(None), + rect_opt: Cell::new(None), + selected: false, + highlighted: false, + overlaps_drag_rect: false, + dir_size: DirSize::NotDirectory, + cut: false, + }) + }) + .collect(); items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) { (true, false) => Ordering::Less, (false, true) => Ordering::Greater, @@ -1158,71 +1144,53 @@ fn uri_to_path(uri: String) -> Option { } pub fn scan_recents(sizes: IconSizes) -> Vec { - let mut recents = Vec::new(); - - match recently_used_xbel::parse_file() { - Ok(recent_files) => { - for bookmark in recent_files.bookmarks { - let uri = bookmark.href; - let path = match uri_to_path(uri) { - None => continue, - Some(path) => path, - }; - let last_edit = match bookmark.modified.parse::>() { - Ok(last_edit) => last_edit, - Err(_) => continue, - }; - let last_visit = match bookmark.visited.parse::>() { - Ok(last_visit) => last_visit, - Err(_) => continue, - }; - let path_exist = path.exists(); - - if path_exist { - let file_name = path.file_name(); - - if let Some(name) = file_name { - let name = name.to_string_lossy().to_string(); - - let metadata = match path.metadata() { - Ok(ok) => ok, - Err(err) => { - log::warn!( - "failed to read metadata for entry at {}: {}", - path.display(), - err - ); - continue; - } - }; - - let item = item_from_entry(path, name, metadata, sizes); - recents.push(( - item, - if last_edit.le(&last_visit) { - last_edit - } else { - last_visit - }, - )); - } - } else { - log::warn!("recent file path not exist: {}", path.display()); - } - } - } + let recent_files = match recently_used_xbel::parse_file() { + Ok(recent_files) => recent_files, Err(err) => { log::warn!("Error reading recent files: {err:?}"); + return Vec::new(); } - } + }; + let mut recents: Vec<_> = recent_files + .bookmarks + .into_iter() + .filter_map(|bookmark| { + let path = uri_to_path(bookmark.href)?; + let last_edit = bookmark.modified.parse::>().ok()?; + let last_visit = bookmark.visited.parse::>().ok()?; - recents.sort_by(|a, b| b.1.cmp(&a.1)); + if path.exists() { + let file_name = path.file_name()?; + let name = file_name.to_string_lossy().to_string(); + + let metadata = match path.metadata() { + Ok(ok) => ok, + Err(err) => { + log::warn!( + "failed to read metadata for entry at {}: {}", + path.display(), + err + ); + return None; + } + }; + + let item = item_from_entry(path, name, metadata, sizes); + Some((item, last_edit.min(last_visit))) + } else { + log::warn!("recent file path not exist: {}", path.display()); + None + } + }) + .collect(); + + recents.sort_by_key(|recent| Reverse(recent.1)); recents.into_iter().take(50).map(|(item, _)| item).collect() } pub fn scan_network(uri: &str, sizes: IconSizes) -> Vec { - for (_key, mounter) in MOUNTERS.iter() { + for mounter in MOUNTERS.values() { match mounter.network_scan(uri, sizes) { Some(Ok(items)) => return items, Some(Err(err)) => { @@ -1250,12 +1218,12 @@ pub fn scan_desktop( } if desktop_config.show_mounted_drives { - for (_mounter_key, mounter) in MOUNTERS.iter() { - for mounter_item in mounter.items(sizes).unwrap_or_default() { - let Some(path) = mounter_item.path() else { - continue; - }; - + for mounter in MOUNTERS.values() { + let Some(mounter_items) = mounter.items(sizes) else { + continue; + }; + items.extend(mounter_items.into_iter().filter_map(|mounter_item| { + let path = mounter_item.path()?; // Get most item data from path let mut item = match item_from_path(&path, sizes) { Ok(item) => item, @@ -1265,7 +1233,7 @@ pub fn scan_desktop( path.display(), err ); - continue; + return None; } }; @@ -1275,13 +1243,13 @@ pub fn scan_desktop( //TODO: use icon size for mounter item icon if let Some(icon) = mounter_item.icon(false) { - item.icon_handle_grid = icon.clone(); - item.icon_handle_list = icon.clone(); + item.icon_handle_grid.clone_from(&icon); + item.icon_handle_list.clone_from(&icon); item.icon_handle_list_condensed = icon; } - items.push(item); - } + Some(item) + })); } } @@ -1415,17 +1383,17 @@ impl Location { } pub fn ancestors(&self) -> Vec<(Self, String)> { - let mut ancestors = Vec::new(); - if let Some(path) = self.path_opt() { - for ancestor in path.ancestors() { - let (name, found_home) = folder_name(ancestor); - ancestors.push((self.with_path(ancestor.to_path_buf()), name)); - if found_home { - break; - } - } - } - ancestors + self.path_opt().map_or_else(Default::default, |path| { + path.ancestors() + .scan(false, |found_home, ancestor| { + (!*found_home).then(|| { + let (name, is_home) = folder_name(ancestor); + *found_home = is_home; + (self.with_path(ancestor.to_path_buf()), name) + }) + }) + .collect() + }) } pub const fn path_opt(&self) -> Option<&PathBuf> { @@ -1438,6 +1406,16 @@ impl Location { } } + pub(crate) fn into_path_opt(self) -> Option { + match self { + Self::Desktop(path, ..) => Some(path), + Self::Path(path) => Some(path), + Self::Search(path, ..) => Some(path), + Self::Network(_, _, path) => path, + _ => None, + } + } + pub fn with_path(&self, path: PathBuf) -> Self { match self { Self::Desktop(_, display, desktop_config) => { @@ -1690,13 +1668,7 @@ impl ItemMetadata { pub fn file_size(&self) -> Option { match self { - Self::Path { metadata, .. } => { - if metadata.is_dir() { - None - } else { - Some(metadata.len()) - } - } + Self::Path { metadata, .. } => (!metadata.is_dir()).then_some(metadata.len()), Self::Trash { metadata, .. } => match metadata.size { TrashItemSize::Bytes(size) => Some(size), TrashItemSize::Entries(_) => None, @@ -2180,8 +2152,8 @@ impl Item { #[cfg(feature = "gvfs")] ItemMetadata::GvfsPath { children_opt, .. } => { // grab the fs::metadata object for gvfs paths since this is run on-demand - if let Some(path) = &self.path_opt() { - file_metadata = fs::metadata(*path).ok(); + if let Some(path) = self.path_opt() { + file_metadata = fs::metadata(path).ok(); } dir_children_count = *children_opt; @@ -2320,9 +2292,7 @@ impl Item { if !settings.is_empty() { let mut section = widget::settings::section(); - for setting in settings { - section = section.add(setting); - } + section = section.extend(settings); column = column.push(section); } @@ -2521,7 +2491,7 @@ fn folder_name>(path: P) -> (String, bool) { // This is not optimized but it helps ensure the same display names match item_from_path(path, IconSizes::default()) { Ok(item) => item.display_name, - Err(_err) => name.to_string_lossy().to_string(), + Err(_err) => name.to_string_lossy().into_owned(), } } } @@ -2533,10 +2503,9 @@ fn folder_name>(path: P) -> (String, bool) { } // parse .hidden file and return files path -fn parse_hidden_file(path: &PathBuf) -> Vec { - let file = match File::open(path) { - Ok(f) => f, - Err(_) => return Vec::new(), +fn parse_hidden_file(path: &PathBuf) -> Box<[String]> { + let Ok(file) = File::open(path) else { + return Default::default(); }; BufReader::new(file) @@ -2645,11 +2614,9 @@ impl Tab { if let Some(ref mut items) = self.items_opt { for item in items.iter_mut() { item.cut = false; - if let Some(location) = &item.location_opt { - if locations - .iter() - .any(|s| location.path_opt().is_some_and(|b| b == s)) - { + if let Some(location_path) = item.location_opt.as_ref().and_then(Location::path_opt) + { + if locations.contains(location_path) { item.cut = true; } } @@ -2658,17 +2625,20 @@ impl Tab { } pub fn selected_locations(&self) -> Vec { - let mut locations = Vec::new(); if let Some(ref items) = self.items_opt { - for item in items { - if item.selected { - if let Some(location) = &item.location_opt { - locations.push(location.clone()); + items + .iter() + .filter_map(|item| { + if item.selected { + item.location_opt.clone() + } else { + None } - } - } + }) + .collect() + } else { + Vec::new() } - locations } pub fn select_all(&mut self) { @@ -3065,13 +3035,15 @@ impl Tab { // a sorted tab. let min = indices .iter() - .position(|&offset| offset == range_min) + .copied() + .position(|offset| offset == range_min) .unwrap_or_default(); // We can't skip `min_real` elements here because the index of // `max` may actually be before `min` in a sorted tab let max = indices .iter() - .position(|&offset| offset == range_max) + .copied() + .position(|offset| offset == range_max) .unwrap_or(indices.len()); let min_real = min.min(max); let max_real = max.max(min); @@ -3096,7 +3068,7 @@ impl Tab { let dont_unset = mod_ctrl || self.column_sort().is_some_and(|l| { l.iter() - .any(|(e_i, e)| Some(e_i) == click_i_opt.as_ref() && e.selected) + .any(|&(e_i, e)| Some(e_i) == click_i_opt && e.selected) }); if let Some(ref mut items) = self.items_opt { for (i, item) in items.iter_mut().enumerate() { @@ -3291,7 +3263,7 @@ impl Tab { match path.map_or_else( || { let items = self.items_opt.as_deref()?; - items.iter().find(|item| item.selected).and_then(|item| { + items.iter().find(|&item| item.selected).and_then(|item| { let location = item.location_opt.as_ref()?; let path = location.path_opt()?; cosmic::desktop::load_desktop_file(&[language.into()], path.into()) @@ -3372,9 +3344,7 @@ impl Tab { if let Some(edit_location) = &mut self.edit_location { edit_location.select(true); } else if self.gallery { - for command in self.update(Message::GalleryNext, modifiers) { - commands.push(command); - } + commands.append(&mut self.update(Message::GalleryNext, modifiers)); } else { if let Some((row, col)) = self.select_focus_pos_opt().or(self.select_last_pos_opt()) @@ -3407,9 +3377,7 @@ impl Tab { } Message::ItemLeft => { if self.gallery { - for command in self.update(Message::GalleryPrevious, modifiers) { - commands.push(command); - } + commands.append(&mut self.update(Message::GalleryPrevious, modifiers)); } else { if let Some((row, col)) = self.select_focus_pos_opt().or(self.select_first_pos_opt()) @@ -3460,9 +3428,7 @@ impl Tab { } Message::ItemRight => { if self.gallery { - for command in self.update(Message::GalleryNext, modifiers) { - commands.push(command); - } + commands.append(&mut self.update(Message::GalleryNext, modifiers)); } else { if let Some((row, col)) = self.select_focus_pos_opt().or(self.select_last_pos_opt()) @@ -3498,9 +3464,7 @@ impl Tab { if let Some(edit_location) = &mut self.edit_location { edit_location.select(false); } else if self.gallery { - for command in self.update(Message::GalleryPrevious, modifiers) { - commands.push(command); - } + commands.append(&mut self.update(Message::GalleryPrevious, modifiers)); } else { if let Some((row, col)) = self.select_focus_pos_opt().or(self.select_first_pos_opt()) @@ -3594,13 +3558,12 @@ impl Tab { } } Message::Reload => { - let mut selected_paths = Vec::new(); //TODO: support keeping selected locations without paths - for location in self.selected_locations() { - if let Some(path) = location.path_opt() { - selected_paths.push(path.clone()); - } - } + let selected_paths = self + .selected_locations() + .into_iter() + .filter_map(Location::into_path_opt) + .collect(); let location = self.location.clone(); self.change_location(&location, None); commands.push(Command::ChangeLocation( @@ -3835,8 +3798,8 @@ impl Tab { ItemThumbnail::Text(_text) => None, }; if let Some(handle) = handle_opt { - item.icon_handle_grid = handle.clone(); - item.icon_handle_list = handle.clone(); + item.icon_handle_grid.clone_from(&handle); + item.icon_handle_list.clone_from(&handle); item.icon_handle_list_condensed = handle; } item.thumbnail_opt = Some(thumbnail); @@ -3908,13 +3871,10 @@ impl Tab { self.dnd_hovered = Some((loc.clone(), Instant::now())); if loc != self.location { commands.push(Command::Iced( - cosmic::Task::perform( - async move { - tokio::time::sleep(HOVER_DURATION).await; - Message::DndHover(loc) - }, - |x| x, - ) + cosmic::Task::future(async move { + tokio::time::sleep(HOVER_DURATION).await; + Message::DndHover(loc) + }) .into(), )); } @@ -3940,7 +3900,7 @@ impl Tab { let location = Location::Path(path); if let Some(ref mut item) = self.parent_item_opt { if item.location_opt.as_ref() == Some(&location) { - item.dir_size = dir_size.clone(); + item.dir_size.clone_from(&dir_size); } } if let Some(ref mut items) = self.items_opt { @@ -4409,7 +4369,7 @@ impl Tab { .into() }; - let heading_row = widget::row::with_children(vec![ + let heading_row = widget::row::with_children([ heading_item(fl!("name"), Length::Fill, HeadingOptions::Name), if self.location == Location::Trash { heading_item( @@ -4443,7 +4403,7 @@ impl Tab { if let Some(edit_location) = &self.edit_location { if let Some(location) = edit_location.resolve() { //TODO: allow editing other locations - if let Some(path) = location.path_opt().cloned() { + if let Some(path) = location.path_opt() { row = row.push( widget::button::custom( widget::icon::from_name("window-close-symbolic").size(16), @@ -4452,7 +4412,7 @@ impl Tab { .padding(space_xxs) .class(theme::Button::Icon), ); - let text_input = widget::text_input("", path.to_string_lossy().to_string()) + let text_input = widget::text_input("", path.to_string_lossy().into_owned()) .id(self.edit_location_id.clone()) .on_input(move |input| { Message::EditLocation(Some( @@ -4632,9 +4592,7 @@ impl Tab { } } - for child in children { - row = row.push(child); - } + row = row.extend(children); let mut column = widget::column::with_capacity(4).padding([0, space_s]); column = column.push(row); column = column.push(accent_rule); @@ -4663,31 +4621,29 @@ impl Tab { pub fn empty_view(&self, has_hidden: bool) -> Element<'_, Message> { let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing; - mouse_area::MouseArea::new(widget::column::with_children(vec![ - widget::container( - widget::column::with_children(match self.mode { - Mode::App | Mode::Dialog(_) => vec![ - widget::icon::from_name("folder-symbolic") - .size(64) - .icon() - .into(), - widget::text::body(if has_hidden { - fl!("empty-folder-hidden") - } else if matches!(self.location, Location::Search(..)) { - fl!("no-results") - } else { - fl!("empty-folder") - }) + mouse_area::MouseArea::new(widget::column::with_children([widget::container( + match self.mode { + Mode::App | Mode::Dialog(_) => widget::column::with_children([ + widget::icon::from_name("folder-symbolic") + .size(64) + .icon() .into(), - ], - Mode::Desktop => Vec::new(), - }) - .align_x(Alignment::Center) - .spacing(space_xxs), - ) - .center(Length::Fill) - .into(), - ])) + widget::text::body(if has_hidden { + fl!("empty-folder-hidden") + } else if matches!(self.location, Location::Search(..)) { + fl!("no-results") + } else { + fl!("empty-folder") + }) + .into(), + ]), + Mode::Desktop => widget::column(), + } + .align_x(Alignment::Center) + .spacing(space_xxs), + ) + .center(Length::Fill) + .into()])) .on_press(|_| Message::Click(None)) .into() } @@ -4769,7 +4725,7 @@ impl Tab { let mut drag_e_i = 0; let mut drag_s_i = 0; - let mut children = Vec::new(); + let mut column = widget::column::with_capacity(2); if let Some(items) = self.column_sort() { let mut count = 0; let mut col = 0; @@ -4921,7 +4877,7 @@ impl Tab { return (None, self.empty_view(hidden > 0), false); } - children.push(grid.into()); + column = column.push(grid); //TODO: HACK If we don't reach the bottom of the view, go ahead and add a spacer to do that { @@ -4945,10 +4901,9 @@ impl Tab { let spacer_height = height.saturating_sub(max_bottom + top_deduct); if spacer_height > 0 { - children.push( - widget::container(Space::with_height(Length::Fixed(spacer_height as f32))) - .into(), - ); + column = column.push(widget::container(Space::with_height(Length::Fixed( + spacer_height as f32, + )))); } } } @@ -4997,13 +4952,11 @@ impl Tab { )), ]; - let mut column = widget::column::with_capacity(buttons.len()) - .align_x(Alignment::Center) - .height(Length::Fixed(item_height as f32)) - .width(Length::Fixed(item_width as f32)); - for button in buttons { - column = column.push(button) - } + let column = + widget::column::with_children(buttons.into_iter().map(Element::from)) + .align_x(Alignment::Center) + .height(Length::Fixed(item_height as f32)) + .width(Length::Fixed(item_width as f32)); dnd_grid = dnd_grid.push(column); dnd_item_i += 1; @@ -5018,13 +4971,12 @@ impl Tab { Element::from(dnd_grid) }); - let mut mouse_area = - mouse_area::MouseArea::new(widget::column::with_children(children).width(Length::Fill)) - .on_press(|_| Message::Click(None)) - .on_auto_scroll(Message::AutoScroll) - .on_drag_end(|_| Message::DragEnd) - .show_drag_rect(self.mode.multiple()) - .on_release(|_| Message::ClickRelease(None)); + let mut mouse_area = mouse_area::MouseArea::new(column.width(Length::Fill)) + .on_press(|_| Message::Click(None)) + .on_auto_scroll(Message::AutoScroll) + .on_drag_end(|_| Message::DragEnd) + .show_drag_rect(self.mode.multiple()) + .on_release(|_| Message::ClickRelease(None)); if self.watch_drag { mouse_area = mouse_area.on_drag(Message::Drag); } @@ -5063,7 +5015,7 @@ impl Tab { }; let row_height = icon_size + 2 * space_xxs; - let mut children: Vec> = Vec::new(); + let mut column = widget::column::with_capacity(3); let mut y: f32 = 0.0; let rule_padding = theme::active().cosmic().corner_radii.radius_xs[0] as u16; @@ -5091,11 +5043,8 @@ impl Tab { } if count > 0 { - children.push( - widget::container(horizontal_rule(1)) - .padding([0, rule_padding]) - .into(), - ); + column = column + .push(widget::container(horizontal_rule(1)).padding([0, rule_padding])); y += 1.0; } @@ -5186,12 +5135,12 @@ impl Tab { }; let row = if condensed { - widget::row::with_children(vec![ + widget::row::with_children([ widget::icon::icon(item.icon_handle_list_condensed.clone()) .content_fit(ContentFit::Contain) .size(icon_size) .into(), - widget::column::with_children(vec![ + widget::column::with_children([ widget::text::body(item.display_name.clone()).into(), //TODO: translate? widget::text::caption(format!("{modified_text} - {size_text}")) @@ -5203,12 +5152,12 @@ impl Tab { .align_y(Alignment::Center) .spacing(space_xxs) } else if is_search { - widget::row::with_children(vec![ + widget::row::with_children([ widget::icon::icon(item.icon_handle_list_condensed.clone()) .content_fit(ContentFit::Contain) .size(icon_size) .into(), - widget::column::with_children(vec![ + widget::column::with_children([ widget::text::body(item.display_name.clone()).into(), widget::text::caption(match item.path_opt() { Some(path) => path.display().to_string(), @@ -5229,7 +5178,7 @@ impl Tab { .align_y(Alignment::Center) .spacing(space_xxs) } else { - widget::row::with_children(vec![ + widget::row::with_children([ widget::icon::icon(item.icon_handle_list.clone()) .content_fit(ContentFit::Contain) .size(icon_size) @@ -5295,12 +5244,12 @@ impl Tab { let dnd_row = if !item.selected { Element::from(Space::with_height(Length::Fixed(f32::from(row_height)))) } else if condensed { - widget::row::with_children(vec![ + widget::row::with_children([ widget::icon::icon(item.icon_handle_list_condensed.clone()) .content_fit(ContentFit::Contain) .size(icon_size) .into(), - widget::column::with_children(vec![ + widget::column::with_children([ widget::text::body(item.display_name.clone()).into(), //TODO: translate? widget::text::body(format!("{modified_text} - {size_text}")) @@ -5312,12 +5261,12 @@ impl Tab { .spacing(space_xxs) .into() } else if is_search { - widget::row::with_children(vec![ + widget::row::with_children([ widget::icon::icon(item.icon_handle_list_condensed.clone()) .content_fit(ContentFit::Contain) .size(icon_size) .into(), - widget::column::with_children(vec![ + widget::column::with_children([ widget::text::body(item.display_name.clone()).into(), widget::text::caption(match item.path_opt() { Some(path) => path.display().to_string(), @@ -5338,7 +5287,7 @@ impl Tab { .spacing(space_xxs) .into() } else { - widget::row::with_children(vec![ + widget::row::with_children([ widget::icon::icon(item.icon_handle_list.clone()) .content_fit(ContentFit::Contain) .size(icon_size) @@ -5378,7 +5327,7 @@ impl Tab { count += 1; y += f32::from(row_height); - children.push(button_row); + column = column.push(button_row); } if count == 0 { @@ -5397,23 +5346,19 @@ impl Tab { let spacer_height = size.height - y - f32::from(top_deduct); if spacer_height > 0. { - children.push( - widget::container(Space::with_height(Length::Fixed(spacer_height))).into(), - ); + column = column.push(widget::container(Space::with_height(spacer_height))); } } let drag_col = (!drag_items.is_empty()) .then(|| Element::from(widget::column::with_children(drag_items))); - let mut mouse_area = mouse_area::MouseArea::new( - widget::column::with_children(children).padding([0, space_s]), - ) - .with_id(Id::new("list-view")) - .on_press(|_| Message::Click(None)) - .on_auto_scroll(Message::AutoScroll) - .on_drag_end(|_| Message::DragEnd) - .show_drag_rect(self.mode.multiple()) - .on_release(|_| Message::ClickRelease(None)); + let mut mouse_area = mouse_area::MouseArea::new(column.padding([0, space_s])) + .with_id(Id::new("list-view")) + .on_press(|_| Message::Click(None)) + .on_auto_scroll(Message::AutoScroll) + .on_drag_end(|_| Message::DragEnd) + .show_drag_rect(self.mode.multiple()) + .on_release(|_| Message::ClickRelease(None)); if self.watch_drag { mouse_area = mouse_area.on_drag(Message::Drag); } @@ -5452,9 +5397,14 @@ impl Tab { .map(|items| { items .iter() - .filter(|item| item.selected) - .filter_map(|item| item.path_opt().cloned()) - .collect::>() + .filter_map(|item| { + if item.selected { + item.path_opt().cloned() + } else { + None + } + }) + .collect::>() }) .unwrap_or_default(); let item_view = @@ -5533,7 +5483,7 @@ impl Tab { if let Some(items) = self.items_opt() { if !items.is_empty() { tab_column = tab_column.push( - widget::layer_container(widget::row::with_children(vec![ + widget::layer_container(widget::row::with_children([ widget::horizontal_space().into(), widget::button::standard(fl!("empty-trash")) .on_press(Message::EmptyTrash) @@ -5549,7 +5499,7 @@ impl Tab { } Location::Network(uri, _display_name, _path) if uri == "network:///" => { tab_column = tab_column.push( - widget::layer_container(widget::row::with_children(vec![ + widget::layer_container(widget::row::with_children([ widget::horizontal_space().into(), widget::button::standard(fl!("add-network-drive")) .on_press(Message::AddNetworkDrive) @@ -5715,7 +5665,7 @@ impl Tab { // Load directory size for selected items if let Some(item) = items .iter() - .find(|item| item.selected) + .find(|&item| item.selected) .or(self.parent_item_opt.as_ref()) { // Item must have a path @@ -5872,7 +5822,7 @@ impl Tab { .cloned() { subscriptions.push(Subscription::run_with_id( - ("tab_complete", path.to_string_lossy().to_string()), + ("tab_complete", path.to_string_lossy().into_owned()), stream::channel(1, |mut output| async move { let message = { let path = path.clone(); @@ -6201,7 +6151,7 @@ mod tests { let top_level = filter_dirs(path)?; let mut result = Vec::new(); for dir in top_level { - let nested_dirs: Vec = filter_dirs(&dir)?.collect(); + let nested_dirs = filter_dirs(&dir)?; result.push(dir); result.extend(nested_dirs); } @@ -6496,10 +6446,11 @@ mod tests { debug!("Shuffled numbers for paths: {base_nums:?}"); let paths: Vec<_> = base_nums .iter() - .map(|&base| path.join(std::iter::repeat_n(base, 255).collect::())) + .copied() + .map(|base| path.join(std::iter::repeat_n(base, 255).collect::())) .collect(); - for (file, &base) in paths.iter().zip(base_nums.iter()) { + for (file, base) in paths.iter().zip(base_nums.into_iter()) { trace!("Creating long file name for {base}"); fs::File::create(file)?; } diff --git a/src/thumbnail_cacher.rs b/src/thumbnail_cacher.rs index 447b848..82cafb3 100644 --- a/src/thumbnail_cacher.rs +++ b/src/thumbnail_cacher.rs @@ -149,9 +149,8 @@ impl ThumbnailCacher { let info = reader.info(); let text_chunks: FxHashMap = info .uncompressed_latin1_text - .clone() - .into_iter() - .map(|chunk| (chunk.keyword, chunk.text)) + .iter() + .map(|chunk| (chunk.keyword.clone(), chunk.text.clone())) .collect(); ( info.width, @@ -222,7 +221,7 @@ impl ThumbnailCacher { // Thumb::URI is required and must match. let thumb_uri = texts .iter() - .find(|text| text.keyword == "Thumb::URI") + .find(|&text| text.keyword == "Thumb::URI") .map(|t| &t.text); if let Some(thumb_uri) = thumb_uri { if *thumb_uri != self.file_uri { @@ -247,7 +246,7 @@ impl ThumbnailCacher { // Thumb::MTime is required and must match. let thumb_mtime = texts .iter() - .find(|text| text.keyword == "Thumb::MTime") + .find(|&text| text.keyword == "Thumb::MTime") .map(|t| &t.text); if let Some(thumb_mtime) = thumb_mtime { let modified = match metadata.modified() { @@ -276,7 +275,7 @@ impl ThumbnailCacher { // Thumb::Size isn't required, but it should be verified if present. let thumb_size = texts .iter() - .find(|text| text.keyword == "Thumb::Size") + .find(|&text| text.keyword == "Thumb::Size") .map(|t| &t.text); if let Some(thumb_size) = thumb_size { let size = metadata.len(); @@ -301,7 +300,7 @@ fn thumbnail_uri(path: &Path) -> io::Result { // and they aren't by the url crate, but the thumbnailer used by // Gnome Files does. In order to share thumbnails and not get duplicates // we should do the same. - let url = url.to_string().replace('[', "%5B").replace(']', "%5D"); + let url = url.as_str().replace('[', "%5B").replace(']', "%5D"); Ok(url) } diff --git a/src/thumbnailer.rs b/src/thumbnailer.rs index 3627fe5..8a22ba9 100644 --- a/src/thumbnailer.rs +++ b/src/thumbnailer.rs @@ -80,30 +80,32 @@ impl ThumbnailerCache { let mut search_dirs = Vec::new(); let xdg_dirs = xdg::BaseDirectories::new(); - if let Some(data_home) = xdg_dirs.get_data_home() { - search_dirs.push(data_home.join("thumbnailers")); - } - for data_dir in xdg_dirs.get_data_dirs() { - search_dirs.push(data_dir.join("thumbnailers")); + if let Some(mut data_home) = xdg_dirs.get_data_home() { + data_home.push("thumbnailers"); + search_dirs.push(data_home); } + search_dirs.extend(xdg_dirs.get_data_dirs().into_iter().map(|mut data_dir| { + data_dir.push("thumbnailers"); + data_dir + })); let mut thumbnailer_paths = Vec::new(); for dir in search_dirs { log::trace!("looking for thumbnailers in {}", dir.display()); match fs::read_dir(&dir) { Ok(entries) => { - for entry_res in entries { - match entry_res { - Ok(entry) => thumbnailer_paths.push(entry.path()), - Err(err) => { + thumbnailer_paths.extend(entries.filter_map(|entry_res| { + entry_res + .inspect_err(|err| { log::warn!( "failed to read entry in directory {}: {}", dir.display(), err - ); - } - } - } + ) + }) + .ok() + .map(|entry| entry.path()) + })); } Err(err) => { log::warn!("failed to read directory {}: {}", dir.display(), err); diff --git a/src/zoom.rs b/src/zoom.rs new file mode 100644 index 0000000..600d0b5 --- /dev/null +++ b/src/zoom.rs @@ -0,0 +1,52 @@ +use std::num::NonZeroU16; + +use crate::{config::IconSizes, tab::View}; + +static DEFAULT_ZOOM: NonZeroU16 = NonZeroU16::new(100).unwrap(); +static MIN_ZOOM: NonZeroU16 = NonZeroU16::new(50).unwrap(); +static MAX_ZOOM: NonZeroU16 = NonZeroU16::new(500).unwrap(); +const ZOOM_STEP: u16 = 25; + +pub(crate) const fn zoom_to_default(view: View, icon_sizes: &mut IconSizes) { + let icon_size = select_resized_icon(view, icon_sizes); + *icon_size = DEFAULT_ZOOM; +} + +pub(crate) fn zoom_in_view(view: View, icon_sizes: &mut IconSizes) { + let icon_size = select_resized_icon(view, icon_sizes); + + let mut step = MIN_ZOOM; + while step <= MAX_ZOOM { + if *icon_size < step { + *icon_size = step; + break; + } + step = step.saturating_add(ZOOM_STEP); + } + if *icon_size > step { + *icon_size = step; + } +} + +pub(crate) fn zoom_out_view(view: View, icon_sizes: &mut IconSizes) { + let icon_size = select_resized_icon(view, icon_sizes); + + let mut step = MAX_ZOOM; + while step >= MIN_ZOOM { + if *icon_size > step { + *icon_size = step; + break; + } + step = NonZeroU16::new(step.get().saturating_sub(ZOOM_STEP)).unwrap(); + } + if *icon_size < step { + *icon_size = step; + } +} + +const fn select_resized_icon(view: View, icon_sizes: &mut IconSizes) -> &mut NonZeroU16 { + match view { + View::Grid => &mut icon_sizes.grid, + View::List => &mut icon_sizes.list, + } +}