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:
parent
5ec14f86b3
commit
783256fe8b
6 changed files with 622 additions and 28 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(¤t).unwrap();
|
||||
let collator = Collator::try_new(&locale, options).unwrap();
|
||||
collator
|
||||
};
|
||||
|
||||
collator
|
||||
});
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fl {
|
||||
($message_id:literal) => {{
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
41
src/tab.rs
41
src/tab.rs
|
|
@ -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)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue