chore: migrate to Rust 2024 edition
This commit is contained in:
parent
6ed7bdb45f
commit
f95869a631
16 changed files with 194 additions and 161 deletions
|
|
@ -2,9 +2,9 @@
|
||||||
name = "cosmic-files"
|
name = "cosmic-files"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
authors = ["Jeremy Soller <jeremy@system76.com>"]
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
rust-version = "1.85.0"
|
rust-version = "1.85"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
vergen = { version = "8", features = ["git", "gitcl"] }
|
vergen = { version = "8", features = ["git", "gitcl"] }
|
||||||
|
|
@ -88,7 +88,7 @@ optional = true
|
||||||
git = "https://github.com/pop-os/libcosmic.git"
|
git = "https://github.com/pop-os/libcosmic.git"
|
||||||
default-features = false
|
default-features = false
|
||||||
#TODO: a11y feature crashes
|
#TODO: a11y feature crashes
|
||||||
features = ["autosize","multi-window", "tokio", "winit", "surface-message"]
|
features = ["autosize", "multi-window", "tokio", "winit", "surface-message"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [
|
default = [
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cosmic-files-applet"
|
name = "cosmic-files-applet"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
@ -11,4 +11,3 @@ zbus = "5"
|
||||||
path = ".."
|
path = ".."
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["desktop", "gvfs", "wayland", "wgpu", "desktop-applet"]
|
features = ["desktop", "gvfs", "wayland", "wgpu", "desktop-applet"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use cosmic_files::operation::recursive::Method;
|
use cosmic_files::operation::recursive::Method;
|
||||||
use cosmic_files::operation::{recursive::Context, Controller, ReplaceResult};
|
use cosmic_files::operation::{Controller, ReplaceResult, recursive::Context};
|
||||||
use std::{error::Error, io, path::PathBuf};
|
use std::{error::Error, io, path::PathBuf};
|
||||||
|
|
||||||
#[compio::main]
|
#[compio::main]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
|
Application, Element,
|
||||||
app::{self, Core, Settings, Task},
|
app::{self, Core, Settings, Task},
|
||||||
executor,
|
executor,
|
||||||
iced::{window, Subscription},
|
iced::{Subscription, window},
|
||||||
widget, Application, Element,
|
widget,
|
||||||
};
|
};
|
||||||
use cosmic_files::dialog::{
|
use cosmic_files::dialog::{
|
||||||
Dialog, DialogChoice, DialogChoiceOption, DialogFilter, DialogFilterPattern, DialogKind,
|
Dialog, DialogChoice, DialogChoiceOption, DialogFilter, DialogFilterPattern, DialogKind,
|
||||||
|
|
|
||||||
62
src/app.rs
62
src/app.rs
|
|
@ -3,23 +3,24 @@
|
||||||
|
|
||||||
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
||||||
use cosmic::iced::{
|
use cosmic::iced::{
|
||||||
|
Limits,
|
||||||
event::wayland::{Event as WaylandEvent, OutputEvent, OverlapNotifyEvent},
|
event::wayland::{Event as WaylandEvent, OutputEvent, OverlapNotifyEvent},
|
||||||
platform_specific::runtime::wayland::layer_surface::{
|
platform_specific::runtime::wayland::layer_surface::{
|
||||||
IcedMargin, IcedOutput, SctkLayerSurfaceSettings,
|
IcedMargin, IcedOutput, SctkLayerSurfaceSettings,
|
||||||
},
|
},
|
||||||
platform_specific::shell::wayland::commands::layer_surface::{
|
platform_specific::shell::wayland::commands::layer_surface::{
|
||||||
destroy_layer_surface, get_layer_surface, Anchor, KeyboardInteractivity, Layer,
|
Anchor, KeyboardInteractivity, Layer, destroy_layer_surface, get_layer_surface,
|
||||||
},
|
},
|
||||||
Limits,
|
|
||||||
};
|
};
|
||||||
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
||||||
use cosmic::iced_winit::commands::overlap_notify::overlap_notify;
|
use cosmic::iced_winit::commands::overlap_notify::overlap_notify;
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
app::{self, context_drawer, Core, Task},
|
Application, ApplicationExt, Element,
|
||||||
|
app::{self, Core, Task, context_drawer},
|
||||||
cosmic_config::{self, ConfigSet},
|
cosmic_config::{self, ConfigSet},
|
||||||
cosmic_theme, executor,
|
cosmic_theme, executor,
|
||||||
iced::{
|
iced::{
|
||||||
self,
|
self, Alignment, Event, Length, Point, Rectangle, Size, Subscription,
|
||||||
clipboard::dnd::DndAction,
|
clipboard::dnd::DndAction,
|
||||||
core::SmolStr,
|
core::SmolStr,
|
||||||
event,
|
event,
|
||||||
|
|
@ -28,7 +29,6 @@ use cosmic::{
|
||||||
stream,
|
stream,
|
||||||
widget::scrollable,
|
widget::scrollable,
|
||||||
window::{self, Event as WindowEvent, Id as WindowId},
|
window::{self, Event as WindowEvent, Id as WindowId},
|
||||||
Alignment, Event, Length, Point, Rectangle, Size, Subscription,
|
|
||||||
},
|
},
|
||||||
iced_runtime::clipboard,
|
iced_runtime::clipboard,
|
||||||
style, surface, theme,
|
style, surface, theme,
|
||||||
|
|
@ -40,13 +40,11 @@ use cosmic::{
|
||||||
segmented_button::{self, Entity},
|
segmented_button::{self, Entity},
|
||||||
vertical_space,
|
vertical_space,
|
||||||
},
|
},
|
||||||
Application, ApplicationExt, Element,
|
|
||||||
};
|
};
|
||||||
use mime_guess::Mime;
|
use mime_guess::Mime;
|
||||||
use notify_debouncer_full::{
|
use notify_debouncer_full::{
|
||||||
new_debouncer,
|
DebouncedEvent, Debouncer, FileIdMap, new_debouncer,
|
||||||
notify::{self, RecommendedWatcher, Watcher},
|
notify::{self, RecommendedWatcher, Watcher},
|
||||||
DebouncedEvent, Debouncer, FileIdMap,
|
|
||||||
};
|
};
|
||||||
use slotmap::Key as SlotMapKey;
|
use slotmap::Key as SlotMapKey;
|
||||||
use std::{
|
use std::{
|
||||||
|
|
@ -65,13 +63,13 @@ use std::{
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use trash::TrashItem;
|
use trash::TrashItem;
|
||||||
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
#[cfg(all(feature = "wayland", feature = "desktop-applet"))]
|
||||||
use wayland_client::{protocol::wl_output::WlOutput, Proxy};
|
use wayland_client::{Proxy, protocol::wl_output::WlOutput};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
|
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
|
||||||
config::{
|
config::{
|
||||||
AppTheme, Config, DesktopConfig, Favorite, IconSizes, TabConfig, TimeConfig, TypeToSearch,
|
AppTheme, Config, DesktopConfig, Favorite, IconSizes, TIME_CONFIG_ID, TabConfig,
|
||||||
TIME_CONFIG_ID,
|
TimeConfig, TypeToSearch,
|
||||||
},
|
},
|
||||||
dialog::{Dialog, DialogKind, DialogMessage, DialogResult},
|
dialog::{Dialog, DialogKind, DialogMessage, DialogResult},
|
||||||
fl, home_dir,
|
fl, home_dir,
|
||||||
|
|
@ -80,14 +78,14 @@ use crate::{
|
||||||
menu,
|
menu,
|
||||||
mime_app::{self, MimeApp, MimeAppCache},
|
mime_app::{self, MimeApp, MimeAppCache},
|
||||||
mime_icon,
|
mime_icon,
|
||||||
mounter::{MounterAuth, MounterItem, MounterItems, MounterKey, MounterMessage, MOUNTERS},
|
mounter::{MOUNTERS, MounterAuth, MounterItem, MounterItems, MounterKey, MounterMessage},
|
||||||
operation::{
|
operation::{
|
||||||
Controller, Operation, OperationError, OperationErrorType, OperationSelection,
|
Controller, Operation, OperationError, OperationErrorType, OperationSelection,
|
||||||
ReplaceResult,
|
ReplaceResult,
|
||||||
},
|
},
|
||||||
spawn_detached::spawn_detached,
|
spawn_detached::spawn_detached,
|
||||||
tab::{
|
tab::{
|
||||||
self, HeadingOptions, ItemMetadata, Location, Tab, HOVER_DURATION, SORT_OPTION_FALLBACK,
|
self, HOVER_DURATION, HeadingOptions, ItemMetadata, Location, SORT_OPTION_FALLBACK, Tab,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::{config::State, dialog::DialogSettings};
|
use crate::{config::State, dialog::DialogSettings};
|
||||||
|
|
@ -2195,7 +2193,7 @@ impl Application for App {
|
||||||
for location in flags.uris {
|
for location in flags.uris {
|
||||||
if let Some(e) = app.nav_model.iter().find(|e| {
|
if let Some(e) = app.nav_model.iter().find(|e| {
|
||||||
app.nav_model.data::<Location>(*e).is_some_and(
|
app.nav_model.data::<Location>(*e).is_some_and(
|
||||||
|l| matches!(l, Location::Network(ref uri, ..) if *uri == location.to_string()),
|
|l| matches!(l, Location::Network(uri, ..) if *uri == location.to_string()),
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
commands.push(cosmic::task::message(cosmic::Action::App(
|
commands.push(cosmic::task::message(cosmic::Action::App(
|
||||||
|
|
@ -3194,7 +3192,11 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to reload metadata for {:?}: {}", path, err);
|
log::warn!(
|
||||||
|
"failed to reload metadata for {:?}: {}",
|
||||||
|
path,
|
||||||
|
err
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO item.thumbnail_opt =
|
//TODO item.thumbnail_opt =
|
||||||
|
|
@ -3283,7 +3285,7 @@ impl Application for App {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
Message::OpenInNewWindow(entity_opt) => match env::current_exe() {
|
Message::OpenInNewWindow(entity_opt) => match env::current_exe() {
|
||||||
Ok(exe) => self
|
Ok(exe) => self
|
||||||
|
|
@ -3307,7 +3309,7 @@ impl Application for App {
|
||||||
self.open_tab(Location::Path(parent), true, Some(vec![path]))
|
self.open_tab(Location::Path(parent), true, Some(vec![path]))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
))
|
));
|
||||||
}
|
}
|
||||||
Message::OpenWithBrowse => match self.dialog_pages.pop_front() {
|
Message::OpenWithBrowse => match self.dialog_pages.pop_front() {
|
||||||
Some((
|
Some((
|
||||||
|
|
@ -4428,14 +4430,14 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
NavMenuAction::OpenInNewTab(entity) => {
|
NavMenuAction::OpenInNewTab(entity) => {
|
||||||
match self.nav_model.data::<Location>(entity) {
|
match self.nav_model.data::<Location>(entity) {
|
||||||
Some(Location::Network(ref uri, ref display_name, path)) => {
|
Some(Location::Network(uri, display_name, path)) => {
|
||||||
return self.open_tab(
|
return self.open_tab(
|
||||||
Location::Network(uri.clone(), display_name.clone(), path.clone()),
|
Location::Network(uri.clone(), display_name.clone(), path.clone()),
|
||||||
false,
|
false,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Some(Location::Path(ref path)) => {
|
Some(Location::Path(path)) => {
|
||||||
return self.open_tab(Location::Path(path.clone()), false, None);
|
return self.open_tab(Location::Path(path.clone()), false, None);
|
||||||
}
|
}
|
||||||
Some(Location::Recents) => {
|
Some(Location::Recents) => {
|
||||||
|
|
@ -5721,7 +5723,7 @@ impl Application for App {
|
||||||
.map(|x| Message::TabMessage(Some(*entity), x)),
|
.map(|x| Message::TabMessage(Some(*entity), x)),
|
||||||
id.clone(),
|
id.clone(),
|
||||||
)
|
)
|
||||||
.into()
|
.into();
|
||||||
}
|
}
|
||||||
None => widget::text("Unknown tab ID").into(),
|
None => widget::text("Unknown tab ID").into(),
|
||||||
}
|
}
|
||||||
|
|
@ -5977,7 +5979,9 @@ impl Application for App {
|
||||||
if let Err(e) = futures::executor::block_on(async {
|
if let Err(e) = futures::executor::block_on(async {
|
||||||
output.send(Message::RescanTrash).await
|
output.send(Message::RescanTrash).await
|
||||||
}) {
|
}) {
|
||||||
log::warn!("trash needs to be rescanned but sending message failed: {e:?}");
|
log::warn!(
|
||||||
|
"trash needs to be rescanned but sending message failed: {e:?}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6062,7 +6066,9 @@ impl Application for App {
|
||||||
if let Err(e) = futures::executor::block_on(async {
|
if let Err(e) = futures::executor::block_on(async {
|
||||||
output.send(Message::RescanRecents).await
|
output.send(Message::RescanRecents).await
|
||||||
}) {
|
}) {
|
||||||
log::warn!("open recents tabs need to be updated but sending message failed: {e:?}");
|
log::warn!(
|
||||||
|
"open recents tabs need to be updated but sending message failed: {e:?}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6215,7 +6221,7 @@ pub(crate) mod test_utils {
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{TempDir, tempdir};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{IconSizes, TabConfig, ThumbCfg},
|
config::{IconSizes, TabConfig, ThumbCfg},
|
||||||
|
|
@ -6274,7 +6280,9 @@ pub(crate) mod test_utils {
|
||||||
// TempDir won't leak resources as long as the destructor runs
|
// TempDir won't leak resources as long as the destructor runs
|
||||||
let root = tempdir()?;
|
let root = tempdir()?;
|
||||||
debug!("Root temp directory: {}", root.as_ref().display());
|
debug!("Root temp directory: {}", root.as_ref().display());
|
||||||
trace!("Creating {files} files and {hidden} hidden files in {dirs} temp dirs with {nested} nested temp dirs");
|
trace!(
|
||||||
|
"Creating {files} files and {hidden} hidden files in {dirs} temp dirs with {nested} nested temp dirs"
|
||||||
|
);
|
||||||
|
|
||||||
// All paths for directories and nested directories
|
// All paths for directories and nested directories
|
||||||
let paths = (0..dirs).flat_map(|_| {
|
let paths = (0..dirs).flat_map(|_| {
|
||||||
|
|
@ -6349,11 +6357,7 @@ pub(crate) mod test_utils {
|
||||||
Ok(path.read_dir()?.filter_map(|entry| {
|
Ok(path.read_dir()?.filter_map(|entry| {
|
||||||
entry.ok().and_then(|entry| {
|
entry.ok().and_then(|entry| {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if path.is_dir() {
|
if path.is_dir() { Some(path) } else { None }
|
||||||
Some(path)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
use std::{any::TypeId, num::NonZeroU16, path::PathBuf};
|
use std::{any::TypeId, num::NonZeroU16, path::PathBuf};
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry},
|
Application,
|
||||||
|
cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry},
|
||||||
iced::Subscription,
|
iced::Subscription,
|
||||||
theme, Application,
|
theme,
|
||||||
};
|
};
|
||||||
use ordermap::OrderMap;
|
use ordermap::OrderMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,25 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
app::{context_drawer, cosmic::Cosmic, Core, Task},
|
Application, ApplicationExt, Element,
|
||||||
|
app::{Core, Task, context_drawer, cosmic::Cosmic},
|
||||||
cosmic_config, cosmic_theme, executor,
|
cosmic_config, cosmic_theme, executor,
|
||||||
iced::{
|
iced::{
|
||||||
self, event,
|
self, Alignment, Event, Length, Size, Subscription, event,
|
||||||
futures::{self, SinkExt},
|
futures::{self, SinkExt},
|
||||||
keyboard::{Event as KeyEvent, Key, Modifiers},
|
keyboard::{Event as KeyEvent, Key, Modifiers},
|
||||||
stream, window, Alignment, Event, Length, Size, Subscription,
|
stream, window,
|
||||||
},
|
},
|
||||||
theme,
|
theme,
|
||||||
widget::{
|
widget::{
|
||||||
self,
|
self,
|
||||||
menu::{key_bind::Modifier, Action as MenuAction, KeyBind},
|
menu::{Action as MenuAction, KeyBind, key_bind::Modifier},
|
||||||
segmented_button,
|
segmented_button,
|
||||||
},
|
},
|
||||||
Application, ApplicationExt, Element,
|
|
||||||
};
|
};
|
||||||
use notify_debouncer_full::{
|
use notify_debouncer_full::{
|
||||||
new_debouncer,
|
DebouncedEvent, Debouncer, FileIdMap, new_debouncer,
|
||||||
notify::{self, RecommendedWatcher, Watcher},
|
notify::{self, RecommendedWatcher, Watcher},
|
||||||
DebouncedEvent, Debouncer, FileIdMap,
|
|
||||||
};
|
};
|
||||||
use recently_used_xbel::update_recently_used;
|
use recently_used_xbel::update_recently_used;
|
||||||
use std::{
|
use std::{
|
||||||
|
|
@ -36,12 +35,12 @@ use std::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind},
|
app::{Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind},
|
||||||
config::{Config, DialogConfig, Favorite, ThumbCfg, TimeConfig, TIME_CONFIG_ID},
|
config::{Config, DialogConfig, Favorite, TIME_CONFIG_ID, ThumbCfg, TimeConfig},
|
||||||
fl, home_dir,
|
fl, home_dir,
|
||||||
key_bind::key_binds,
|
key_bind::key_binds,
|
||||||
localize::LANGUAGE_SORTER,
|
localize::LANGUAGE_SORTER,
|
||||||
menu,
|
menu,
|
||||||
mounter::{MounterItem, MounterItems, MounterKey, MounterMessage, MOUNTERS},
|
mounter::{MOUNTERS, MounterItem, MounterItems, MounterKey, MounterMessage},
|
||||||
tab::{self, ItemMetadata, Location, Tab},
|
tab::{self, ItemMetadata, Location, Tab},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1452,7 +1451,11 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::warn!("failed to reload metadata for {:?}: {}", path, err);
|
log::warn!(
|
||||||
|
"failed to reload metadata for {:?}: {}",
|
||||||
|
path,
|
||||||
|
err
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO item.thumbnail_opt =
|
//TODO item.thumbnail_opt =
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use i18n_embed::{
|
use i18n_embed::{
|
||||||
fluent::{fluent_language_loader, FluentLanguageLoader},
|
|
||||||
DefaultLocalizer, LanguageLoader, Localizer,
|
DefaultLocalizer, LanguageLoader, Localizer,
|
||||||
|
fluent::{FluentLanguageLoader, fluent_language_loader},
|
||||||
};
|
};
|
||||||
use icu::locid::Locale;
|
use icu::locid::Locale;
|
||||||
use icu_collator::{Collator, CollatorOptions, Numeric};
|
use icu_collator::{Collator, CollatorOptions, Numeric};
|
||||||
|
|
|
||||||
12
src/menu.rs
12
src/menu.rs
|
|
@ -1,18 +1,18 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
|
Element,
|
||||||
app::Core,
|
app::Core,
|
||||||
iced::{
|
iced::{
|
||||||
advanced::widget::text::Style as TextStyle, keyboard::Modifiers, Alignment, Background,
|
Alignment, Background, Border, Length, advanced::widget::text::Style as TextStyle,
|
||||||
Border, Length,
|
keyboard::Modifiers,
|
||||||
},
|
},
|
||||||
theme,
|
theme,
|
||||||
widget::{
|
widget::{
|
||||||
self, button, column, container, divider, horizontal_space,
|
self, Row, button, column, container, divider, horizontal_space,
|
||||||
menu::{self, key_bind::KeyBind, ItemHeight, ItemWidth, MenuBar},
|
menu::{self, ItemHeight, ItemWidth, MenuBar, key_bind::KeyBind},
|
||||||
responsive_menu_bar, text, Row,
|
responsive_menu_bar, text,
|
||||||
},
|
},
|
||||||
Element,
|
|
||||||
};
|
};
|
||||||
use i18n_embed::LanguageLoader;
|
use i18n_embed::LanguageLoader;
|
||||||
use mime_guess::Mime;
|
use mime_guess::Mime;
|
||||||
|
|
|
||||||
|
|
@ -346,7 +346,11 @@ impl MimeAppCache {
|
||||||
{
|
{
|
||||||
apps.push(MimeApp::from(app));
|
apps.push(MimeApp::from(app));
|
||||||
} else {
|
} else {
|
||||||
log::info!("failed to add association for {:?}: application {:?} not found", mime, filename);
|
log::info!(
|
||||||
|
"failed to add association for {:?}: application {:?} not found",
|
||||||
|
mime,
|
||||||
|
filename
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -385,7 +389,11 @@ impl MimeAppCache {
|
||||||
if found {
|
if found {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
log::debug!("failed to set default for {:?}: application {:?} not found", mime, filename);
|
log::debug!(
|
||||||
|
"failed to set default for {:?}: application {:?} not found",
|
||||||
|
mime,
|
||||||
|
filename
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -625,10 +633,12 @@ mod tests {
|
||||||
let command = commands.first().unwrap();
|
let command = commands.first().unwrap();
|
||||||
|
|
||||||
assert_eq!("/usr/games/gzdoom", command.get_program().to_str().unwrap());
|
assert_eq!("/usr/games/gzdoom", command.get_program().to_str().unwrap());
|
||||||
assert!(paths
|
assert!(
|
||||||
.iter()
|
paths
|
||||||
.zip(command.get_args())
|
.iter()
|
||||||
.all(|(&expected, actual)| expected == actual.to_string_lossy()));
|
.zip(command.get_args())
|
||||||
|
.all(|(&expected, actual)| expected == actual.to_string_lossy())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -669,10 +679,12 @@ mod tests {
|
||||||
assert_eq!(paths.len(), command.get_args().count());
|
assert_eq!(paths.len(), command.get_args().count());
|
||||||
|
|
||||||
assert_eq!("/usr/bin/mpv", command.get_program().to_str().unwrap());
|
assert_eq!("/usr/bin/mpv", command.get_program().to_str().unwrap());
|
||||||
assert!(paths
|
assert!(
|
||||||
.iter()
|
paths
|
||||||
.zip(command.get_args())
|
.iter()
|
||||||
.all(|(&expected, actual)| expected == actual.to_string_lossy()));
|
.zip(command.get_args())
|
||||||
|
.all(|(&expected, actual)| expected == actual.to_string_lossy())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -695,11 +707,12 @@ mod tests {
|
||||||
assert_eq!(args.len() + paths.len(), command.get_args().count());
|
assert_eq!(args.len() + paths.len(), command.get_args().count());
|
||||||
|
|
||||||
assert_eq!("/usr/bin/flatpak", command.get_program().to_str().unwrap());
|
assert_eq!("/usr/bin/flatpak", command.get_program().to_str().unwrap());
|
||||||
assert!(args
|
assert!(
|
||||||
.iter()
|
args.iter()
|
||||||
.chain(paths.iter())
|
.chain(paths.iter())
|
||||||
.zip(command.get_args())
|
.zip(command.get_args())
|
||||||
.all(|(&expected, actual)| expected == actual.to_string_lossy()));
|
.all(|(&expected, actual)| expected == actual.to_string_lossy())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -719,10 +732,12 @@ mod tests {
|
||||||
"/usr/games/roguelike",
|
"/usr/games/roguelike",
|
||||||
command.get_program().to_str().unwrap()
|
command.get_program().to_str().unwrap()
|
||||||
);
|
);
|
||||||
assert!(paths
|
assert!(
|
||||||
.iter()
|
paths
|
||||||
.zip(command.get_args())
|
.iter()
|
||||||
.all(|(&expected, actual)| expected == actual.to_string_lossy()));
|
.zip(command.get_args())
|
||||||
|
.all(|(&expected, actual)| expected == actual.to_string_lossy())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -751,11 +766,13 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!("/usr/bin/flatpak", command.get_program().to_str().unwrap());
|
assert_eq!("/usr/bin/flatpak", command.get_program().to_str().unwrap());
|
||||||
assert!(args_leading
|
assert!(
|
||||||
.iter()
|
args_leading
|
||||||
.chain(paths.iter())
|
.iter()
|
||||||
.chain(args_trailing.iter())
|
.chain(paths.iter())
|
||||||
.zip(command.get_args())
|
.chain(args_trailing.iter())
|
||||||
.all(|(&expected, actual)| expected == actual.to_string_lossy()));
|
.zip(command.get_args())
|
||||||
|
.all(|(&expected, actual)| expected == actual.to_string_lossy())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
iced::{futures::SinkExt, stream, Subscription},
|
Task,
|
||||||
widget, Task,
|
iced::{Subscription, futures::SinkExt, stream},
|
||||||
|
widget,
|
||||||
};
|
};
|
||||||
use gio::{glib, prelude::*};
|
use gio::{glib, prelude::*};
|
||||||
use std::{any::TypeId, cell::Cell, future::pending, path::PathBuf, sync::Arc};
|
use std::{any::TypeId, cell::Cell, future::pending, path::PathBuf, sync::Arc};
|
||||||
use tokio::sync::{mpsc, Mutex};
|
use tokio::sync::{Mutex, mpsc};
|
||||||
|
|
||||||
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
use super::{Mounter, MounterAuth, MounterItem, MounterItems, MounterMessage};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use cosmic::{iced::Subscription, widget, Task};
|
use cosmic::{Task, iced::Subscription, widget};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::{collections::BTreeMap, fmt, path::PathBuf, sync::Arc};
|
use std::{collections::BTreeMap, fmt, path::PathBuf, sync::Arc};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ use std::time::Instant;
|
||||||
|
|
||||||
use crate::tab::DOUBLE_CLICK_DURATION;
|
use crate::tab::DOUBLE_CLICK_DURATION;
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
|
Element, Renderer, Theme,
|
||||||
iced_core::{
|
iced_core::{
|
||||||
|
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
|
||||||
border::Border,
|
border::Border,
|
||||||
event::{self, Event},
|
event::{self, Event},
|
||||||
layout,
|
layout,
|
||||||
|
|
@ -12,11 +14,9 @@ use cosmic::{
|
||||||
overlay,
|
overlay,
|
||||||
renderer::{self, Quad, Renderer as _},
|
renderer::{self, Quad, Renderer as _},
|
||||||
touch,
|
touch,
|
||||||
widget::{tree, Operation, Tree},
|
widget::{Operation, Tree, tree},
|
||||||
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Vector, Widget,
|
|
||||||
},
|
},
|
||||||
widget::Id,
|
widget::Id,
|
||||||
Element, Renderer, Theme,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Emit messages on mouse events.
|
/// Emit messages on mouse events.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
spawn_detached::spawn_detached,
|
spawn_detached::spawn_detached,
|
||||||
tab,
|
tab,
|
||||||
};
|
};
|
||||||
use cosmic::iced::futures::{channel::mpsc::Sender, SinkExt};
|
use cosmic::iced::futures::{SinkExt, channel::mpsc::Sender};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
fmt::Formatter,
|
fmt::Formatter,
|
||||||
|
|
@ -14,7 +14,7 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use tokio::sync::{mpsc, Mutex as TokioMutex};
|
use tokio::sync::{Mutex as TokioMutex, mpsc};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
use zip::AesMode::Aes256;
|
use zip::AesMode::Aes256;
|
||||||
|
|
||||||
|
|
@ -140,9 +140,11 @@ async fn copy_or_move(
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
log::info!("renamed {from:?} to {to:?}");
|
log::info!("renamed {from:?} to {to:?}");
|
||||||
false
|
false
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::info!("failed to rename {from:?} to {to:?}, fallback to recursive move: {err}");
|
log::info!(
|
||||||
|
"failed to rename {from:?} to {to:?}, fallback to recursive move: {err}"
|
||||||
|
);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1155,7 +1157,7 @@ mod tests {
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
use cosmic::iced::futures::{channel::mpsc, StreamExt};
|
use cosmic::iced::futures::{StreamExt, channel::mpsc};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use test_log::test;
|
use test_log::test;
|
||||||
use tokio::sync;
|
use tokio::sync;
|
||||||
|
|
@ -1163,11 +1165,11 @@ mod tests {
|
||||||
use super::{Controller, Operation, OperationError, OperationSelection, ReplaceResult};
|
use super::{Controller, Operation, OperationError, OperationSelection, ReplaceResult};
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
test_utils::{
|
|
||||||
empty_fs, filter_dirs, filter_files, simple_fs, NAME_LEN, NUM_DIRS, NUM_FILES,
|
|
||||||
NUM_HIDDEN, NUM_NESTED,
|
|
||||||
},
|
|
||||||
DialogPage, Message,
|
DialogPage, Message,
|
||||||
|
test_utils::{
|
||||||
|
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, empty_fs, filter_dirs,
|
||||||
|
filter_files, simple_fs,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fl,
|
fl,
|
||||||
};
|
};
|
||||||
|
|
@ -1198,10 +1200,13 @@ mod tests {
|
||||||
match msg {
|
match msg {
|
||||||
Message::DialogPush(DialogPage::Replace { tx, .. }) => {
|
Message::DialogPush(DialogPage::Replace { tx, .. }) => {
|
||||||
debug!("[{id}] Replace request");
|
debug!("[{id}] Replace request");
|
||||||
tx.send(ReplaceResult::Cancel).await.expect("Sending a response to a replace request should succeed")
|
tx.send(ReplaceResult::Cancel)
|
||||||
|
.await
|
||||||
|
.expect("Sending a response to a replace request should succeed")
|
||||||
}
|
}
|
||||||
_ => unreachable!("Only [ `Message::PendingProgress`, `Message::DialogPush(DialogPage::Replace)` ] are sent from operation"),
|
_ => unreachable!(
|
||||||
|
"Only [ `Message::PendingProgress`, `Message::DialogPush(DialogPage::Replace)` ] are sent from operation"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
use compio::BufResult;
|
||||||
use compio::buf::{IntoInner, IoBuf};
|
use compio::buf::{IntoInner, IoBuf};
|
||||||
use compio::io::{AsyncReadAt, AsyncWriteAt};
|
use compio::io::{AsyncReadAt, AsyncWriteAt};
|
||||||
use compio::BufResult;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
@ -9,7 +9,7 @@ use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::operation::OperationError;
|
use crate::operation::OperationError;
|
||||||
|
|
||||||
use super::{copy_unique_path, Controller, OperationSelection, ReplaceResult};
|
use super::{Controller, OperationSelection, ReplaceResult, copy_unique_path};
|
||||||
|
|
||||||
pub enum Method {
|
pub enum Method {
|
||||||
Copy,
|
Copy,
|
||||||
|
|
|
||||||
120
src/tab.rs
120
src/tab.rs
|
|
@ -1,6 +1,16 @@
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
cosmic_theme, font,
|
Element, cosmic_theme, font,
|
||||||
iced::{
|
iced::{
|
||||||
|
Alignment,
|
||||||
|
Border,
|
||||||
|
Color,
|
||||||
|
ContentFit,
|
||||||
|
Length,
|
||||||
|
Point,
|
||||||
|
Rectangle,
|
||||||
|
Size,
|
||||||
|
Subscription,
|
||||||
|
Vector,
|
||||||
advanced::{
|
advanced::{
|
||||||
graphics,
|
graphics,
|
||||||
text::{self, Paragraph},
|
text::{self, Paragraph},
|
||||||
|
|
@ -17,36 +27,24 @@ use cosmic::{
|
||||||
scrollable::{self, AbsoluteOffset, Viewport},
|
scrollable::{self, AbsoluteOffset, Viewport},
|
||||||
},
|
},
|
||||||
window,
|
window,
|
||||||
Alignment,
|
|
||||||
Border,
|
|
||||||
Color,
|
|
||||||
ContentFit,
|
|
||||||
Length,
|
|
||||||
Point,
|
|
||||||
Rectangle,
|
|
||||||
Size,
|
|
||||||
Subscription,
|
|
||||||
Vector,
|
|
||||||
},
|
},
|
||||||
iced_core::{mouse::ScrollDelta, widget::tree},
|
iced_core::{mouse::ScrollDelta, widget::tree},
|
||||||
theme,
|
theme,
|
||||||
widget::{
|
widget::{
|
||||||
self,
|
self, DndDestination, DndSource, Id, Space, Widget,
|
||||||
menu::{action::MenuAction, key_bind::KeyBind},
|
menu::{action::MenuAction, key_bind::KeyBind},
|
||||||
DndDestination, DndSource, Id, Space, Widget,
|
|
||||||
},
|
},
|
||||||
Element,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, Datelike, Timelike, Utc};
|
use chrono::{DateTime, Datelike, Timelike, Utc};
|
||||||
use i18n_embed::LanguageLoader;
|
use i18n_embed::LanguageLoader;
|
||||||
use icu::datetime::{
|
use icu::datetime::{
|
||||||
options::{components, preferences},
|
|
||||||
DateTimeFormatter, DateTimeFormatterOptions,
|
DateTimeFormatter, DateTimeFormatterOptions,
|
||||||
|
options::{components, preferences},
|
||||||
};
|
};
|
||||||
use image::ImageDecoder;
|
use image::ImageDecoder;
|
||||||
use jxl_oxide::integration::JxlDecoder;
|
use jxl_oxide::integration::JxlDecoder;
|
||||||
use mime_guess::{mime, Mime};
|
use mime_guess::{Mime, mime};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use ordermap::OrderMap;
|
use ordermap::OrderMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -62,7 +60,7 @@ use std::{
|
||||||
io::{BufRead, BufReader},
|
io::{BufRead, BufReader},
|
||||||
os::unix::fs::MetadataExt,
|
os::unix::fs::MetadataExt,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{atomic, Arc, LazyLock, Mutex, RwLock},
|
sync::{Arc, LazyLock, Mutex, RwLock, atomic},
|
||||||
time::{Duration, Instant, SystemTime},
|
time::{Duration, Instant, SystemTime},
|
||||||
};
|
};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
|
|
@ -73,7 +71,7 @@ use walkdir::WalkDir;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{Action, PreviewItem, PreviewKind},
|
app::{Action, PreviewItem, PreviewKind},
|
||||||
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
|
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
|
||||||
config::{DesktopConfig, IconSizes, TabConfig, ThumbCfg, ICON_SCALE_MAX, ICON_SIZE_GRID},
|
config::{DesktopConfig, ICON_SCALE_MAX, ICON_SIZE_GRID, IconSizes, TabConfig, ThumbCfg},
|
||||||
dialog::DialogKind,
|
dialog::DialogKind,
|
||||||
fl,
|
fl,
|
||||||
localize::{LANGUAGE_SORTER, LOCALE},
|
localize::{LANGUAGE_SORTER, LOCALE},
|
||||||
|
|
@ -4020,11 +4018,7 @@ impl Tab {
|
||||||
|
|
||||||
fn column_sort(&self) -> Option<Vec<(usize, &Item)>> {
|
fn column_sort(&self) -> Option<Vec<(usize, &Item)>> {
|
||||||
let check_reverse = |ord: Ordering, sort: bool| {
|
let check_reverse = |ord: Ordering, sort: bool| {
|
||||||
if sort {
|
if sort { ord } else { ord.reverse() }
|
||||||
ord
|
|
||||||
} else {
|
|
||||||
ord.reverse()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let mut items: Vec<_> = self.items_opt.as_ref()?.iter().enumerate().collect();
|
let mut items: Vec<_> = self.items_opt.as_ref()?.iter().enumerate().collect();
|
||||||
let (sort_name, sort_direction, folders_first) = self.sort_options();
|
let (sort_name, sort_direction, folders_first) = self.sort_options();
|
||||||
|
|
@ -4658,29 +4652,31 @@ impl Tab {
|
||||||
pub fn empty_view(&self, has_hidden: bool) -> Element<Message> {
|
pub fn empty_view(&self, has_hidden: bool) -> Element<Message> {
|
||||||
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
|
let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing;
|
||||||
|
|
||||||
mouse_area::MouseArea::new(widget::column::with_children(vec![widget::container(
|
mouse_area::MouseArea::new(widget::column::with_children(vec![
|
||||||
widget::column::with_children(match self.mode {
|
widget::container(
|
||||||
Mode::App | Mode::Dialog(_) => vec![
|
widget::column::with_children(match self.mode {
|
||||||
widget::icon::from_name("folder-symbolic")
|
Mode::App | Mode::Dialog(_) => vec![
|
||||||
.size(64)
|
widget::icon::from_name("folder-symbolic")
|
||||||
.icon()
|
.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")
|
||||||
|
})
|
||||||
.into(),
|
.into(),
|
||||||
widget::text::body(if has_hidden {
|
],
|
||||||
fl!("empty-folder-hidden")
|
Mode::Desktop => Vec::new(),
|
||||||
} else if matches!(self.location, Location::Search(..)) {
|
})
|
||||||
fl!("no-results")
|
.align_x(Alignment::Center)
|
||||||
} else {
|
.spacing(space_xxs),
|
||||||
fl!("empty-folder")
|
)
|
||||||
})
|
.center(Length::Fill)
|
||||||
.into(),
|
.into(),
|
||||||
],
|
]))
|
||||||
Mode::Desktop => Vec::new(),
|
|
||||||
})
|
|
||||||
.align_x(Alignment::Center)
|
|
||||||
.spacing(space_xxs),
|
|
||||||
)
|
|
||||||
.center(Length::Fill)
|
|
||||||
.into()]))
|
|
||||||
.on_press(|_| Message::Click(None))
|
.on_press(|_| Message::Click(None))
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
@ -6109,11 +6105,11 @@ mod tests {
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use test_log::test;
|
use test_log::test;
|
||||||
|
|
||||||
use super::{respond_to_scroll_direction, scan_path, Location, Message, Tab};
|
use super::{Location, Message, Tab, respond_to_scroll_direction, scan_path};
|
||||||
use crate::{
|
use crate::{
|
||||||
app::test_utils::{
|
app::test_utils::{
|
||||||
assert_eq_tab_path, empty_fs, eq_path_item, filter_dirs, read_dir_sorted, simple_fs,
|
NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED, assert_eq_tab_path, empty_fs,
|
||||||
tab_click_new, NAME_LEN, NUM_DIRS, NUM_FILES, NUM_HIDDEN, NUM_NESTED,
|
eq_path_item, filter_dirs, read_dir_sorted, simple_fs, tab_click_new,
|
||||||
},
|
},
|
||||||
config::{IconSizes, TabConfig, ThumbCfg},
|
config::{IconSizes, TabConfig, ThumbCfg},
|
||||||
};
|
};
|
||||||
|
|
@ -6166,12 +6162,16 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// All directories (simple_fs only produces one nested layer)
|
// All directories (simple_fs only produces one nested layer)
|
||||||
let dirs: Vec<PathBuf> = filter_dirs(path)?
|
let dirs: Vec<PathBuf> = {
|
||||||
.flat_map(|dir| {
|
let top_level = filter_dirs(path)?;
|
||||||
filter_dirs(&dir).map(|nested_dirs| std::iter::once(dir).chain(nested_dirs))
|
let mut result = Vec::new();
|
||||||
})
|
for dir in top_level {
|
||||||
.flatten()
|
let nested_dirs: Vec<PathBuf> = filter_dirs(&dir)?.collect();
|
||||||
.collect();
|
result.push(dir);
|
||||||
|
result.extend(nested_dirs);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
};
|
||||||
assert!(
|
assert!(
|
||||||
dirs.len() == NUM_DIRS + NUM_DIRS * NUM_NESTED,
|
dirs.len() == NUM_DIRS + NUM_DIRS * NUM_NESTED,
|
||||||
"Sanity check: Have {} dirs instead of {}",
|
"Sanity check: Have {} dirs instead of {}",
|
||||||
|
|
@ -6210,10 +6210,12 @@ mod tests {
|
||||||
assert_eq!(entries.len(), actual.len());
|
assert_eq!(entries.len(), actual.len());
|
||||||
|
|
||||||
// Correct files should be scanned
|
// Correct files should be scanned
|
||||||
assert!(entries
|
assert!(
|
||||||
.into_iter()
|
entries
|
||||||
.zip(actual.into_iter())
|
.into_iter()
|
||||||
.all(|(path, item)| eq_path_item(&path, &item)));
|
.zip(actual.into_iter())
|
||||||
|
.all(|(path, item)| eq_path_item(&path, &item))
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -6483,7 +6485,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn mode_calculations() {
|
fn mode_calculations() {
|
||||||
use super::{
|
use super::{
|
||||||
get_mode_part, set_mode_part, MODE_SHIFT_GROUP, MODE_SHIFT_OTHER, MODE_SHIFT_USER,
|
MODE_SHIFT_GROUP, MODE_SHIFT_OTHER, MODE_SHIFT_USER, get_mode_part, set_mode_part,
|
||||||
};
|
};
|
||||||
for user in 0..=7 {
|
for user in 0..=7 {
|
||||||
for group in 0..=7 {
|
for group in 0..=7 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue