fix: Panic on long numerical file names

Closes: #258

[lexical_sort](https://github.com/Aloso/lexical-sort) is currently
unmaintained. The author recommends switching to the `icu` crate which
is maintained by the Unicode Consortium.
This commit is contained in:
Josh Megnauth 2024-07-08 02:00:52 -04:00
parent 5ec14f86b3
commit 783256fe8b
No known key found for this signature in database
GPG key ID: 70813183462EFAD3
6 changed files with 622 additions and 28 deletions

View file

@ -41,6 +41,7 @@ use std::{
time::{self, Instant},
};
use crate::localize::LANGUAGE_SORTER;
use crate::tab::HOVER_DURATION;
use crate::{
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
@ -503,7 +504,7 @@ impl App {
}
}
// Sort by name lexically
nav_items.sort_by(|a, b| lexical_sort::natural_lexical_cmp(&a.1.name(), &b.1.name()));
nav_items.sort_by(|a, b| LANGUAGE_SORTER.compare(&a.1.name(), &b.1.name()));
// Add items to nav model
for (key, item) in nav_items {
nav_model = nav_model.insert(|mut b| {
@ -2653,7 +2654,7 @@ pub(crate) mod test_utils {
match (a.is_dir(), b.is_dir()) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => lexical_sort::natural_lexical_cmp(
_ => LANGUAGE_SORTER.compare(
a.file_name()
.expect("temp entries should have names")
.to_str()

View file

@ -1,9 +1,13 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::str::FromStr;
use i18n_embed::{
fluent::{fluent_language_loader, FluentLanguageLoader},
DefaultLocalizer, LanguageLoader, Localizer,
};
use icu::collator::{Collator, CollatorOptions, Numeric};
use icu_provider::DataLocale;
use once_cell::sync::Lazy;
use rust_embed::RustEmbed;
@ -21,6 +25,20 @@ pub static LANGUAGE_LOADER: Lazy<FluentLanguageLoader> = Lazy::new(|| {
loader
});
pub static LANGUAGE_SORTER: Lazy<Collator> = Lazy::new(|| {
let mut options = CollatorOptions::new();
options.numeric = Some(Numeric::On);
let collator = {
let current = LANGUAGE_LOADER.current_language().to_string();
let locale = DataLocale::from_str(&current).unwrap();
let collator = Collator::try_new(&locale, options).unwrap();
collator
};
collator
});
#[macro_export]
macro_rules! fl {
($message_id:literal) => {{

View file

@ -94,6 +94,8 @@ impl MimeAppCache {
// Only available when using desktop feature of libcosmic, which only works on Unix-likes
#[cfg(feature = "desktop")]
pub fn reload(&mut self) {
use crate::localize::LANGUAGE_SORTER;
let start = Instant::now();
self.cache.clear();
@ -245,7 +247,7 @@ impl MimeAppCache {
apps.sort_by(|a, b| match (a.is_default, b.is_default) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => lexical_sort::natural_lexical_cmp(&a.name, &b.name),
_ => LANGUAGE_SORTER.compare(&a.name, &b.name),
});
}

View file

@ -43,6 +43,7 @@ use std::{
};
use crate::clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste};
use crate::localize::LANGUAGE_SORTER;
use crate::{
app::{self, Action},
config::{IconSizes, TabConfig, ICON_SCALE_MAX, ICON_SIZE_GRID},
@ -313,7 +314,7 @@ pub fn scan_path(tab_path: &PathBuf, sizes: IconSizes) -> Vec<Item> {
items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => lexical_sort::natural_lexical_cmp(&a.name, &b.name),
_ => LANGUAGE_SORTER.compare(&a.name, &b.name),
});
items
}
@ -498,7 +499,7 @@ pub fn scan_trash(sizes: IconSizes) -> Vec<Item> {
items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => lexical_sort::natural_lexical_cmp(&a.name, &b.name),
_ => LANGUAGE_SORTER.compare(&a.name, &b.name),
});
items
}
@ -1763,15 +1764,12 @@ impl Tab {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => check_reverse(
lexical_sort::natural_lexical_cmp(&a.1.name, &b.1.name),
LANGUAGE_SORTER.compare(&a.1.name, &b.1.name),
heading_sort,
),
}
} else {
check_reverse(
lexical_sort::natural_lexical_cmp(&a.1.name, &b.1.name),
heading_sort,
)
check_reverse(LANGUAGE_SORTER.compare(&a.1.name, &b.1.name), heading_sort)
}
}),
HeadingOptions::Modified => {
@ -2893,7 +2891,7 @@ impl Tab {
#[cfg(test)]
mod tests {
use std::{io, path::PathBuf};
use std::{fs, io, path::PathBuf};
use cosmic::iced_runtime::keyboard::Modifiers;
use log::{debug, trace};
@ -3173,6 +3171,33 @@ mod tests {
Ok(())
}
#[test]
fn sort_long_number_file_names() -> io::Result<()> {
let fs = empty_fs()?;
let path = fs.path();
// Create files with names 255 characters long that only contain a single number
// Example: 0000...0 for 255 characters
// https://en.wikipedia.org/wiki/Filename#Comparison_of_filename_limitations
let mut base_nums: Vec<_> = ('0'..'9').collect();
fastrand::shuffle(&mut base_nums);
debug!("Shuffled numbers for paths: {base_nums:?}");
let paths: Vec<_> = base_nums
.iter()
.map(|&base| path.join(std::iter::repeat(base).take(255).collect::<String>()))
.collect();
for (file, &base) in paths.iter().zip(base_nums.iter()) {
trace!("Creating long file name for {base}");
fs::File::create(file)?;
}
debug!("Creating tab for directory of long file names");
Tab::new(Location::Path(path.into()), TabConfig::default());
Ok(())
}
}
#[derive(Clone)]