diff --git a/Cargo.lock b/Cargo.lock index 83663f9..e8e9d6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1496,6 +1496,7 @@ dependencies = [ "recently-used-xbel", "regex", "rust-embed", + "rustc-hash 2.1.1", "serde", "shlex", "slotmap", diff --git a/Cargo.toml b/Cargo.toml index 5f8b89b..51fad23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ notify-rust = { version = "4", optional = true } open = "5.3.2" paste = "1.0" regex = "1" +rustc-hash = "2.1" serde = { version = "1", features = ["serde_derive"] } shlex = { version = "1.3" } tempfile = "3" diff --git a/src/app.rs b/src/app.rs index dbeabe3..c07f3e5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -47,10 +47,11 @@ use notify_debouncer_full::{ DebouncedEvent, Debouncer, RecommendedCache, new_debouncer, notify::{self, RecommendedWatcher}, }; +use rustc_hash::{FxHashMap, FxHashSet}; use slotmap::Key as SlotMapKey; use std::{ any::TypeId, - collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}, + collections::{BTreeMap, BTreeSet, HashMap, VecDeque}, env, fmt, fs, future::Future, io, @@ -67,6 +68,7 @@ use trash::TrashItem; use wayland_client::{Proxy, protocol::wl_output::WlOutput}; use crate::{ + FxOrderMap, clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}, config::{ AppTheme, Config, DesktopConfig, Favorite, IconSizes, TIME_CONFIG_ID, TabConfig, @@ -654,16 +656,16 @@ pub struct App { dialog_pages: DialogPages, dialog_text_input: widget::Id, key_binds: HashMap, - margin: HashMap, + margin: FxHashMap, mime_app_cache: MimeAppCache, modifiers: Modifiers, - mounter_items: HashMap, + mounter_items: FxHashMap, must_save_sort_names: bool, network_drive_connecting: Option<(MounterKey, String)>, network_drive_input: String, #[cfg(feature = "notify")] notification_opt: Option>>, - overlap: HashMap, + overlap: FxHashMap, pending_operation_id: u64, pending_operations: BTreeMap, progress_operations: BTreeSet, @@ -673,17 +675,17 @@ pub struct App { search_id: widget::Id, size: Option, #[cfg(all(feature = "wayland", feature = "desktop-applet"))] - layer_sizes: HashMap, + layer_sizes: FxHashMap, #[cfg(all(feature = "wayland", feature = "desktop-applet"))] - surface_ids: HashMap, + surface_ids: FxHashMap, #[cfg(all(feature = "wayland", feature = "desktop-applet"))] - surface_names: HashMap, + surface_names: FxHashMap, toasts: widget::toaster::Toasts, watcher_opt: Option<( Debouncer, - HashSet, + FxHashSet, )>, - windows: HashMap, + windows: FxHashMap, nav_dnd_hover: Option<(Location, Instant)>, tab_dnd_hover: Option<(Entity, Instant)>, nav_drag_id: DragId, @@ -699,7 +701,7 @@ impl App { // Associate all paths to its MIME type // This allows handling paths as groups if possible, such as launching a single video // player that is passed every path. - let mut groups: HashMap> = HashMap::new(); + let mut groups: FxHashMap> = FxHashMap::default(); let mut all_archives = true; let supported_archive_types = crate::archive::SUPPORTED_ARCHIVE_TYPES .iter() @@ -915,7 +917,7 @@ impl App { #[cfg(all(feature = "wayland", feature = "desktop-applet"))] fn handle_overlap(&mut self) { - let mut overlaps: HashMap<_, _> = self + let mut overlaps: FxHashMap<_, _> = self .windows .keys() .map(|k| (*k, (0., 0., 0., 0.))) @@ -1573,7 +1575,7 @@ impl App { fn update_watcher(&mut self) -> Task { if let Some((mut watcher, old_paths)) = self.watcher_opt.take() { - let mut new_paths = HashSet::new(); + 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() { @@ -1942,7 +1944,7 @@ impl App { fn get_apps_for_mime(&self, mime_type: &Mime) -> Vec<(&MimeApp, MimeAppMatch)> { let mut results = Vec::new(); - let mut dedupe = HashSet::new(); + let mut dedupe = FxHashSet::default(); // start with exact matches for mime_app in self.mime_app_cache.get(mime_type) { @@ -2124,16 +2126,16 @@ impl Application for App { dialog_pages: DialogPages::new(), dialog_text_input: widget::Id::unique(), key_binds, - margin: HashMap::new(), + margin: FxHashMap::default(), mime_app_cache: MimeAppCache::new(), modifiers: Modifiers::empty(), - mounter_items: HashMap::new(), + mounter_items: FxHashMap::default(), must_save_sort_names: false, network_drive_connecting: None, network_drive_input: String::new(), #[cfg(feature = "notify")] notification_opt: None, - overlap: HashMap::new(), + overlap: FxHashMap::default(), pending_operation_id: 0, pending_operations: BTreeMap::new(), progress_operations: BTreeSet::new(), @@ -2143,12 +2145,12 @@ impl Application for App { search_id: widget::Id::unique(), size: None, #[cfg(all(feature = "wayland", feature = "desktop-applet"))] - surface_ids: HashMap::new(), + surface_ids: FxHashMap::default(), #[cfg(all(feature = "wayland", feature = "desktop-applet"))] - surface_names: HashMap::new(), + surface_names: FxHashMap::default(), toasts: widget::toaster::Toasts::new(Message::CloseToast), watcher_opt: None, - windows: HashMap::new(), + windows: FxHashMap::default(), nav_dnd_hover: None, tab_dnd_hover: None, nav_drag_id: DragId::new(), @@ -2156,7 +2158,7 @@ impl Application for App { auto_scroll_speed: None, file_dialog_opt: None, #[cfg(all(feature = "wayland", feature = "desktop-applet"))] - layer_sizes: HashMap::new(), + layer_sizes: FxHashMap::default(), }; let mut commands = vec![app.update_config()]; @@ -3211,7 +3213,7 @@ impl Application for App { Message::NotifyWatcher(mut watcher_wrapper) => match watcher_wrapper.watcher_opt.take() { Some(watcher) => { - self.watcher_opt = Some((watcher, HashSet::new())); + self.watcher_opt = Some((watcher, FxHashSet::default())); return self.update_watcher(); } None => { @@ -4678,8 +4680,8 @@ impl Application for App { Message::SaveSortNames => { self.must_save_sort_names = false; if let Some(state_handler) = self.state_handler.as_ref() { - if let Err(err) = - state_handler.set::>( + if let Err(err) = state_handler + .set::>( "sort_names", self.state.sort_names.clone(), ) diff --git a/src/config.rs b/src/config.rs index d66abe8..ada8dcb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,10 +8,10 @@ use cosmic::{ iced::Subscription, theme, }; -use ordermap::OrderMap; use serde::{Deserialize, Serialize}; use crate::{ + FxOrderMap, app::App, tab::{HeadingOptions, Location, View}, }; @@ -115,13 +115,13 @@ pub enum TypeToSearch { #[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(default)] pub struct State { - pub sort_names: ordermap::OrderMap, + pub sort_names: FxOrderMap, } impl Default for State { fn default() -> Self { Self { - sort_names: OrderMap::from_iter(dirs::download_dir().into_iter().map(|dir| { + sort_names: FxOrderMap::from_iter(dirs::download_dir().into_iter().map(|dir| { ( Location::Path(dir).normalize().to_string(), (HeadingOptions::Modified, false), diff --git a/src/dialog.rs b/src/dialog.rs index 341c527..27ad491 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -25,9 +25,10 @@ use notify_debouncer_full::{ notify::{self, RecommendedWatcher}, }; use recently_used_xbel::update_recently_used; +use rustc_hash::{FxHashMap, FxHashSet}; use std::{ any::TypeId, - collections::{HashMap, HashSet, VecDeque}, + collections::{HashMap, VecDeque}, env, fmt, fs, num::NonZeroU16, path::PathBuf, @@ -509,7 +510,7 @@ struct App { filter_selected: Option, filename_id: widget::Id, modifiers: Modifiers, - mounter_items: HashMap, + mounter_items: FxHashMap, nav_model: segmented_button::SingleSelectModel, result_opt: Option, search_id: widget::Id, @@ -517,7 +518,7 @@ struct App { key_binds: HashMap, watcher_opt: Option<( Debouncer, - HashSet, + FxHashSet, )>, auto_scroll_speed: Option, } @@ -871,7 +872,7 @@ impl App { fn update_watcher(&mut self) -> Task { if let Some((mut watcher, old_paths)) = self.watcher_opt.take() { - let mut new_paths = HashSet::new(); + let mut new_paths = FxHashSet::default(); if let Some(path) = &self.tab.location.path_opt() { new_paths.insert(path.to_path_buf()); } @@ -981,7 +982,7 @@ impl Application for App { filter_selected: None, filename_id: widget::Id::unique(), modifiers: Modifiers::empty(), - mounter_items: HashMap::new(), + mounter_items: FxHashMap::default(), nav_model: segmented_button::ModelBuilder::default().build(), result_opt: None, search_id: widget::Id::unique(), @@ -1519,7 +1520,7 @@ impl Application for App { Message::NotifyWatcher(mut watcher_wrapper) => match watcher_wrapper.watcher_opt.take() { Some(watcher) => { - self.watcher_opt = Some((watcher, HashSet::new())); + self.watcher_opt = Some((watcher, FxHashSet::default())); return self.update_watcher(); } None => { diff --git a/src/lib.rs b/src/lib.rs index 253f627..fe3aae8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,8 @@ pub mod tab; mod thumbnail_cacher; mod thumbnailer; +pub(crate) type FxOrderMap = ordermap::OrderMap; + pub(crate) fn err_str(err: T) -> String { err.to_string() } diff --git a/src/mime_app.rs b/src/mime_app.rs index 51b0beb..c582c5e 100644 --- a/src/mime_app.rs +++ b/src/mime_app.rs @@ -5,9 +5,9 @@ use cosmic::desktop; use cosmic::widget; pub use mime_guess::Mime; +use rustc_hash::FxHashMap; use std::{ cmp::Ordering, - collections::HashMap, env, ffi::OsStr, fs, io, @@ -221,8 +221,8 @@ fn filename_eq(path_opt: &Option, filename: &str) -> bool { pub struct MimeAppCache { apps: Vec, - cache: HashMap>, - icons: HashMap>, + cache: FxHashMap>, + icons: FxHashMap>, terminals: Vec, } @@ -230,8 +230,8 @@ impl MimeAppCache { pub fn new() -> Self { let mut mime_app_cache = Self { apps: Vec::new(), - cache: HashMap::new(), - icons: HashMap::new(), + cache: FxHashMap::default(), + icons: FxHashMap::default(), terminals: Vec::new(), }; mime_app_cache.reload(); diff --git a/src/mime_icon.rs b/src/mime_icon.rs index 367d535..73674ee 100644 --- a/src/mime_icon.rs +++ b/src/mime_icon.rs @@ -2,8 +2,8 @@ use cosmic::widget::icon; use mime_guess::Mime; +use rustc_hash::FxHashMap; use std::{ - collections::HashMap, fs, path::Path, sync::{LazyLock, Mutex}, @@ -18,14 +18,14 @@ struct MimeIconKey { } struct MimeIconCache { - cache: HashMap>, + cache: FxHashMap>, shared_mime_info: xdg_mime::SharedMimeInfo, } impl MimeIconCache { pub fn new() -> Self { Self { - cache: HashMap::new(), + cache: FxHashMap::default(), shared_mime_info: xdg_mime::SharedMimeInfo::new(), } } diff --git a/src/tab.rs b/src/tab.rs index 6ddc1c4..b9f779f 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -49,7 +49,7 @@ use icu::{ use image::ImageDecoder; use jxl_oxide::integration::JxlDecoder; use mime_guess::{Mime, mime}; -use ordermap::OrderMap; +use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use std::{ borrow::Cow, @@ -73,6 +73,7 @@ use trash::TrashItemSize; use walkdir::WalkDir; use crate::{ + FxOrderMap, app::{Action, PreviewItem, PreviewKind}, clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}, config::{DesktopConfig, ICON_SCALE_MAX, ICON_SIZE_GRID, IconSizes, TabConfig, ThumbCfg}, @@ -100,9 +101,9 @@ const THUMBNAIL_SIZE: u32 = (ICON_SIZE_GRID as u32) * (ICON_SCALE_MAX as u32); pub static THUMB_SEMAPHORE: LazyLock = LazyLock::new(|| tokio::sync::Semaphore::const_new(num_cpus::get())); -pub(crate) static SORT_OPTION_FALLBACK: LazyLock> = +pub(crate) static SORT_OPTION_FALLBACK: LazyLock> = LazyLock::new(|| { - HashMap::from_iter(dirs::download_dir().into_iter().map(|dir| { + FxHashMap::from_iter(dirs::download_dir().into_iter().map(|dir| { ( Location::Path(dir).normalize().to_string(), (HeadingOptions::Modified, false), @@ -131,8 +132,8 @@ static MODE_NAMES: LazyLock> = LazyLock::new(|| { ] }); -static SPECIAL_DIRS: LazyLock> = LazyLock::new(|| { - let mut special_dirs = HashMap::new(); +static SPECIAL_DIRS: LazyLock> = LazyLock::new(|| { + let mut special_dirs = FxHashMap::default(); if let Some(dir) = dirs::document_dir() { special_dirs.insert(dir, "folder-documents"); } @@ -534,25 +535,17 @@ pub enum FsKind { pub fn fs_kind(metadata: &Metadata) -> FsKind { //TODO: method to reload remote filesystems dynamically //TODO: fix for https://github.com/eminence/procfs/issues/262 - static DEVICES: LazyLock> = LazyLock::new(|| { - let mut devices = HashMap::new(); + static DEVICES: LazyLock> = LazyLock::new(|| { + let mut devices = FxHashMap::default(); match procfs::process::Process::myself() { Ok(process) => match process.mountinfo() { Ok(mount_infos) => { - for mount_info in mount_infos.iter() { + devices = FxHashMap::from_iter(mount_infos.iter().filter_map(|mount_info| { let mut parts = mount_info.majmin.split(':'); - let Some(major_str) = parts.next() else { - continue; - }; - let Some(minor_str) = parts.next() else { - continue; - }; - let Ok(major) = major_str.parse::() else { - continue; - }; - let Ok(minor) = minor_str.parse::() else { - continue; - }; + let major_str = parts.next()?; + let minor_str = parts.next()?; + let major = major_str.parse::().ok()?; + let minor = minor_str.parse::().ok()?; let dev = libc::makedev(major, minor); //TODO: make sure this list is exhaustive let kind = match mount_info.fs_type.as_str() { @@ -561,8 +554,8 @@ pub fn fs_kind(metadata: &Metadata) -> FsKind { "fuse.gvfsd-fuse" => FsKind::Gvfs, _ => FsKind::Local, }; - devices.insert(dev, kind); - } + Some((dev, kind)) + })); } Err(err) => { log::warn!("failed to get mount info: {err}"); @@ -2525,7 +2518,7 @@ impl Tab { location: Location, config: TabConfig, thumb_config: ThumbCfg, - sorting_options: Option<&OrderMap>, + sorting_options: Option<&FxOrderMap>, scrollable_id: widget::Id, window_id: Option, ) -> Self { diff --git a/src/thumbnail_cacher.rs b/src/thumbnail_cacher.rs index acdd90b..28da5c6 100644 --- a/src/thumbnail_cacher.rs +++ b/src/thumbnail_cacher.rs @@ -1,7 +1,7 @@ use image::DynamicImage; use md5::{Digest, Md5}; +use rustc_hash::FxHashMap; use std::{ - collections::HashMap, error::Error, fs::{self, File}, io::{self, BufReader, BufWriter}, @@ -143,7 +143,7 @@ impl ThumbnailCacher { let mut reader = decoder.read_info()?; let (width, height, color_type, bit_depth, mut text_chunks) = { let info = reader.info(); - let text_chunks: HashMap = info + let text_chunks: FxHashMap = info .uncompressed_latin1_text .clone() .into_iter() diff --git a/src/thumbnailer.rs b/src/thumbnailer.rs index d0cacac..91b73a5 100644 --- a/src/thumbnailer.rs +++ b/src/thumbnailer.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: GPL-3.0-only use mime_guess::Mime; +use rustc_hash::FxHashMap; use std::{ - collections::HashMap, fs, path::Path, process, @@ -56,13 +56,13 @@ impl Thumbnailer { } pub struct ThumbnailerCache { - cache: HashMap>, + cache: FxHashMap>, } impl ThumbnailerCache { pub fn new() -> Self { let mut thumbnailer_cache = Self { - cache: HashMap::new(), + cache: FxHashMap::default(), }; thumbnailer_cache.reload(); thumbnailer_cache