diff --git a/Cargo.lock b/Cargo.lock index f2aa41cc..db3dd0a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -550,6 +550,12 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "deunicode" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" + [[package]] name = "digest" version = "0.10.7" @@ -2915,6 +2921,7 @@ dependencies = [ "anyhow", "bitflags 2.5.0", "crossterm", + "deunicode", "dirs", "filetime", "futures", diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index 0ab02b91..c3116b27 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -106,18 +106,20 @@ keymap = [ { on = [ "N" ], run = "find_arrow --previous", desc = "Go to previous found file" }, # Sorting - { on = [ ",", "m" ], run = "sort modified --dir-first", desc = "Sort by modified time" }, - { on = [ ",", "M" ], run = "sort modified --reverse --dir-first", desc = "Sort by modified time (reverse)" }, - { on = [ ",", "c" ], run = "sort created --dir-first", desc = "Sort by created time" }, - { on = [ ",", "C" ], run = "sort created --reverse --dir-first", desc = "Sort by created time (reverse)" }, - { on = [ ",", "e" ], run = "sort extension --dir-first", desc = "Sort by extension" }, - { on = [ ",", "E" ], run = "sort extension --reverse --dir-first", desc = "Sort by extension (reverse)" }, - { on = [ ",", "a" ], run = "sort alphabetical --dir-first", desc = "Sort alphabetically" }, - { on = [ ",", "A" ], run = "sort alphabetical --reverse --dir-first", desc = "Sort alphabetically (reverse)" }, - { on = [ ",", "n" ], run = "sort natural --dir-first", desc = "Sort naturally" }, - { on = [ ",", "N" ], run = "sort natural --reverse --dir-first", desc = "Sort naturally (reverse)" }, - { on = [ ",", "s" ], run = "sort size --dir-first", desc = "Sort by size" }, - { on = [ ",", "S" ], run = "sort size --reverse --dir-first", desc = "Sort by size (reverse)" }, + { on = [ ",", "m" ], run = "sort modified --dir-first", desc = "Sort by modified time" }, + { on = [ ",", "M" ], run = "sort modified --reverse --dir-first", desc = "Sort by modified time (reverse)" }, + { on = [ ",", "c" ], run = "sort created --dir-first", desc = "Sort by created time" }, + { on = [ ",", "C" ], run = "sort created --reverse --dir-first", desc = "Sort by created time (reverse)" }, + { on = [ ",", "e" ], run = "sort extension --dir-first", desc = "Sort by extension" }, + { on = [ ",", "E" ], run = "sort extension --reverse --dir-first", desc = "Sort by extension (reverse)" }, + { on = [ ",", "a" ], run = "sort alphabetical --dir-first", desc = "Sort alphabetically" }, + { on = [ ",", "A" ], run = "sort alphabetical --reverse --dir-first", desc = "Sort alphabetically (reverse)" }, + { on = [ ",", "n" ], run = "sort natural --dir-first", desc = "Sort naturally" }, + { on = [ ",", "N" ], run = "sort natural --reverse --dir-first", desc = "Sort naturally (reverse)" }, + { on = [ ",", "d" ], run = "sort deunicode_natural --dir-first", desc = "Sort naturally (deunicode)" }, + { on = [ ",", "D" ], run = "sort deunicode_natural --reverse --dir-first", desc = "Sort naturally (deunicode + reverse)" }, + { on = [ ",", "s" ], run = "sort size --dir-first", desc = "Sort by size" }, + { on = [ ",", "S" ], run = "sort size --reverse --dir-first", desc = "Sort by size (reverse)" }, # Tabs { on = [ "t" ], run = "tab_create --current", desc = "Create a new tab using the current path" }, diff --git a/yazi-config/src/manager/sorting.rs b/yazi-config/src/manager/sorting.rs index 6d060090..9e95ffac 100644 --- a/yazi-config/src/manager/sorting.rs +++ b/yazi-config/src/manager/sorting.rs @@ -13,6 +13,7 @@ pub enum SortBy { Extension, Alphabetical, Natural, + DeunicodeNatural, Size, } @@ -27,6 +28,7 @@ impl FromStr for SortBy { "extension" => Self::Extension, "alphabetical" => Self::Alphabetical, "natural" => Self::Natural, + "deunicode_natural" => Self::DeunicodeNatural, "size" => Self::Size, _ => bail!("invalid sort_by value: {s}"), }) @@ -48,6 +50,7 @@ impl Display for SortBy { Self::Extension => "extension", Self::Alphabetical => "alphabetical", Self::Natural => "natural", + Self::DeunicodeNatural => "deunicode_natural", Self::Size => "size", }) } diff --git a/yazi-core/src/folder/sorter.rs b/yazi-core/src/folder/sorter.rs index 5130ac68..83845bf4 100644 --- a/yazi-core/src/folder/sorter.rs +++ b/yazi-core/src/folder/sorter.rs @@ -1,7 +1,7 @@ use std::{cmp::Ordering, collections::HashMap, mem}; use yazi_config::manager::SortBy; -use yazi_shared::{fs::{File, Url}, natsort}; +use yazi_shared::{deunicode_natsort, fs::{File, Url}, natsort}; #[derive(Clone, Copy, Default, PartialEq)] pub struct FilesSorter { @@ -52,7 +52,8 @@ impl FilesSorter { if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord } }), SortBy::Alphabetical => items.sort_unstable_by(by_alphabetical), - SortBy::Natural => self.sort_naturally(items), + SortBy::Natural => self.sort_naturally(items, false), + SortBy::DeunicodeNatural => self.sort_naturally(items, true), SortBy::Size => items.sort_unstable_by(|a, b| { let aa = if a.is_dir() { sizes.get(&a.url).copied() } else { None }; let bb = if b.is_dir() { sizes.get(&b.url).copied() } else { None }; @@ -62,7 +63,7 @@ impl FilesSorter { } } - fn sort_naturally(&self, items: &mut Vec) { + fn sort_naturally(&self, items: &mut Vec, deunicoded: bool) { let mut indices = Vec::with_capacity(items.len()); let mut entities = Vec::with_capacity(items.len()); for (i, file) in items.iter().enumerate() { @@ -76,7 +77,11 @@ impl FilesSorter { return promote; } - let ordering = natsort(entities[a], entities[b], !self.sensitive); + let ordering = match deunicoded { + true => deunicode_natsort(entities[a], entities[b], !self.sensitive), + false => natsort(entities[a], entities[b], !self.sensitive) + }; + if self.reverse { ordering.reverse() } else { ordering } }); diff --git a/yazi-shared/Cargo.toml b/yazi-shared/Cargo.toml index 098b132d..531a4ab3 100644 --- a/yazi-shared/Cargo.toml +++ b/yazi-shared/Cargo.toml @@ -12,6 +12,7 @@ repository = "https://github.com/sxyazi/yazi" anyhow = "1.0.83" bitflags = "2.5.0" crossterm = "0.27.0" +deunicode = "1.6.0" dirs = "5.0.1" filetime = "0.2.23" futures = "0.3.30" diff --git a/yazi-shared/src/natsort.rs b/yazi-shared/src/natsort.rs index 70e25969..da854441 100644 --- a/yazi-shared/src/natsort.rs +++ b/yazi-shared/src/natsort.rs @@ -6,6 +6,8 @@ use std::cmp::Ordering; +use deunicode::deunicode; + macro_rules! return_unless_equal { ($ord:expr) => { match $ord { @@ -123,6 +125,17 @@ pub fn natsort(left: &[u8], right: &[u8], insensitive: bool) -> Ordering { } } +pub fn deunicode_natsort(left: &[u8], right: &[u8], insensitive: bool) -> Ordering { + let utf8_left = std::str::from_utf8(left); + let utf8_right = std::str::from_utf8(right); + match (utf8_left, utf8_right) { + (Ok(l), Ok(r)) => natsort(deunicode(l).as_bytes(), deunicode(r).as_bytes(), insensitive), + (Ok(l), Err(_)) => natsort(deunicode(l).as_bytes(), right, insensitive), + (Err(_), Ok(r)) => natsort(left, deunicode(r).as_bytes(), insensitive), + _ => natsort(left, right, insensitive), + } +} + #[cfg(test)] mod tests { use super::*; @@ -133,6 +146,12 @@ mod tests { assert_eq!(left, right); } + fn cmp_deun(left: &[&str]) { + let mut right = left.to_vec(); + right.sort_by(|a, b| deunicode_natsort(a.as_bytes(), b.as_bytes(), true)); + assert_eq!(left, right); + } + #[test] fn test_natsort() { let dates = vec!["1999-3-3", "1999-12-25", "2000-1-2", "2000-1-10", "2000-3-23"]; @@ -176,4 +195,33 @@ mod tests { cmp(&fractions); cmp(&words); } + + #[test] + fn test_deunicode_natsort() { + let deun = vec![ + "Acorn", + "Ætt", + "Alpha", + "Átriu", + "Attention", + "Beta", + "Elemér", + "Érvényes", + "Große", + "Grotto", + "Hedvig", + "Ilona", + "Írott", + "Olga", + "Órmotlan", + "Öveges", + "Őzike", + "Ubul", + "Űr", + "Útvonal", + "Üveg", + ]; + + cmp_deun(&deun) + } }