From e4575ab5fec56c215e79619233454f6c1cf738da Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Sun, 8 Feb 2026 15:25:59 +0100 Subject: [PATCH] Use natural sort for filenames --- .cargo/release-nightly.toml | 1 - crates/edit/src/bin/edit/draw_filepicker.rs | 9 +----- crates/edit/src/icu.rs | 32 ++++++++++++++++++++- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/.cargo/release-nightly.toml b/.cargo/release-nightly.toml index f294dca85b6..0ee4e3260d1 100644 --- a/.cargo/release-nightly.toml +++ b/.cargo/release-nightly.toml @@ -13,4 +13,3 @@ rustflags = [ [unstable] panic-immediate-abort = true build-std = ["std", "panic_abort"] -build-std-features = ["default", "optimize_for_size"] diff --git a/crates/edit/src/bin/edit/draw_filepicker.rs b/crates/edit/src/bin/edit/draw_filepicker.rs index d6e9d3b73f3..2c7c74c7b47 100644 --- a/crates/edit/src/bin/edit/draw_filepicker.rs +++ b/crates/edit/src/bin/edit/draw_filepicker.rs @@ -344,14 +344,7 @@ fn draw_dialog_saveas_refresh_files(state: &mut State) { entries.sort_unstable_by(|a, b| { let a = a.as_bytes(); let b = b.as_bytes(); - - let a_is_dir = a.last() == Some(&b'/'); - let b_is_dir = b.last() == Some(&b'/'); - - match b_is_dir.cmp(&a_is_dir) { - Ordering::Equal => icu::compare_strings(a, b), - other => other, - } + icu::compare_strings(a, b) }); } diff --git a/crates/edit/src/icu.rs b/crates/edit/src/icu.rs index a99acac7cc6..0588e108968 100644 --- a/crates/edit/src/icu.rs +++ b/crates/edit/src/icu.rs @@ -742,6 +742,27 @@ pub fn compare_strings(a: &[u8], b: &[u8]) -> Ordering { if let Ok(f) = init_if_needed() { let mut status = icu_ffi::U_ZERO_ERROR; coll = (f.ucol_open)(c"".as_ptr(), &mut status); + // Turns on Unicode normalization. I'm not 100% sure if it's needed, but it only has a + // small-ish performance impact and sounds like it's required for correct filename sorting. + (f.ucol_setAttribute)( + coll, + icu_ffi::UCOL_NORMALIZATION_MODE, + icu_ffi::UCOL_ON, + &mut status, + ); + // Ensure that "file2" < "file10", even though '2' > '1'. + // NOTE: This has a _huge_ performance impact. It's roughly 5x slower for our purpose of + // sorting filenames. If it becomes an issue, we could use `ucol_getSortKey` (only +25%). + // (`ucol_strcollUTF8` is faster if `UCOL_NUMERIC_COLLATION` isn't used.) + (f.ucol_setAttribute)( + coll, + icu_ffi::UCOL_NUMERIC_COLLATION, + icu_ffi::UCOL_ON, + &mut status, + ); + if status.is_failure() { + coll = null_mut(); + } } ROOT_COLLATOR = Some(coll); @@ -920,6 +941,7 @@ struct LibraryFunctions { // LIBICUI18N_PROC_NAMES ucol_open: icu_ffi::ucol_open, + ucol_setAttribute: icu_ffi::ucol_setAttribute, ucol_strcollUTF8: icu_ffi::ucol_strcollUTF8, uregex_open: icu_ffi::uregex_open, uregex_close: icu_ffi::uregex_close, @@ -954,8 +976,9 @@ const LIBICUUC_PROC_NAMES: [*const c_char; 10] = [ ]; // Found in libicui18n.so on UNIX, icuin.dll/icu.dll on Windows. -const LIBICUI18N_PROC_NAMES: [*const c_char; 11] = [ +const LIBICUI18N_PROC_NAMES: [*const c_char; 12] = [ proc_name!("ucol_open"), + proc_name!("ucol_setAttribute"), proc_name!("ucol_strcollUTF8"), proc_name!("uregex_open"), proc_name!("uregex_close"), @@ -1162,6 +1185,13 @@ mod icu_ffi { pub type ucol_open = unsafe extern "C" fn(loc: *const c_char, status: &mut UErrorCode) -> *mut UCollator; + pub type ucol_setAttribute = + unsafe extern "C" fn(coll: *mut UCollator, attr: i32, value: i32, status: &mut UErrorCode); + + pub const UCOL_NORMALIZATION_MODE: i32 = 4; + pub const UCOL_NUMERIC_COLLATION: i32 = 7; + pub const UCOL_ON: i32 = 17; + pub type ucol_strcollUTF8 = unsafe extern "C" fn( coll: *mut UCollator, source: *const u8,