mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-24 08:11:04 +00:00
179 lines
3.5 KiB
Rust
179 lines
3.5 KiB
Rust
// A natural sort implementation in Rust.
|
|
// Copyright (c) 2023, sxyazi.
|
|
//
|
|
// This is a port of the C version of Martin Pool's `strnatcmp.c`:
|
|
// http://sourcefrog.net/projects/natsort/
|
|
|
|
use std::cmp::Ordering;
|
|
|
|
macro_rules! return_unless_equal {
|
|
($ord:expr) => {
|
|
match $ord {
|
|
Ordering::Equal => {}
|
|
ord => return ord,
|
|
}
|
|
};
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn compare_left(left: &[u8], right: &[u8], li: &mut usize, ri: &mut usize) -> Ordering {
|
|
let mut l;
|
|
let mut r;
|
|
|
|
loop {
|
|
l = left.get(*li);
|
|
r = right.get(*ri);
|
|
|
|
match (l.is_some_and(|b| b.is_ascii_digit()), r.is_some_and(|b| b.is_ascii_digit())) {
|
|
(true, true) => {
|
|
return_unless_equal!(unsafe { l.unwrap_unchecked().cmp(r.unwrap_unchecked()) })
|
|
}
|
|
(true, false) => return Ordering::Greater,
|
|
(false, true) => return Ordering::Less,
|
|
(false, false) => return Ordering::Equal,
|
|
}
|
|
|
|
*li += 1;
|
|
*ri += 1;
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn compare_right(left: &[u8], right: &[u8], li: &mut usize, ri: &mut usize) -> Ordering {
|
|
let mut l;
|
|
let mut r;
|
|
let mut bias = Ordering::Equal;
|
|
|
|
loop {
|
|
l = left.get(*li);
|
|
r = right.get(*ri);
|
|
|
|
match (l.is_some_and(|b| b.is_ascii_digit()), r.is_some_and(|b| b.is_ascii_digit())) {
|
|
(true, true) => {
|
|
if bias == Ordering::Equal {
|
|
bias = unsafe { l.unwrap_unchecked().cmp(r.unwrap_unchecked()) };
|
|
}
|
|
}
|
|
(true, false) => return Ordering::Greater,
|
|
(false, true) => return Ordering::Less,
|
|
(false, false) => return bias,
|
|
}
|
|
|
|
*li += 1;
|
|
*ri += 1;
|
|
}
|
|
}
|
|
|
|
pub fn natsort(left: &[u8], right: &[u8], insensitive: bool) -> Ordering {
|
|
let mut li = 0;
|
|
let mut ri = 0;
|
|
|
|
let mut l = left.get(li);
|
|
let mut r = right.get(ri);
|
|
|
|
macro_rules! left_next {
|
|
() => {{
|
|
li += 1;
|
|
l = left.get(li);
|
|
}};
|
|
}
|
|
|
|
macro_rules! right_next {
|
|
() => {{
|
|
ri += 1;
|
|
r = right.get(ri);
|
|
}};
|
|
}
|
|
|
|
loop {
|
|
while l.is_some_and(|c| c.is_ascii_whitespace()) {
|
|
left_next!();
|
|
}
|
|
while r.is_some_and(|c| c.is_ascii_whitespace()) {
|
|
right_next!();
|
|
}
|
|
|
|
match (l, r) {
|
|
(Some(&ll), Some(&rr)) => {
|
|
if ll.is_ascii_digit() && rr.is_ascii_digit() {
|
|
if ll == b'0' || rr == b'0' {
|
|
return_unless_equal!(compare_left(left, right, &mut li, &mut ri));
|
|
} else {
|
|
return_unless_equal!(compare_right(left, right, &mut li, &mut ri));
|
|
}
|
|
|
|
l = left.get(li);
|
|
r = right.get(ri);
|
|
continue;
|
|
}
|
|
|
|
if insensitive {
|
|
return_unless_equal!(ll.to_ascii_lowercase().cmp(&rr.to_ascii_lowercase()));
|
|
} else {
|
|
return_unless_equal!(ll.cmp(&rr));
|
|
}
|
|
}
|
|
(Some(_), None) => return Ordering::Greater,
|
|
(None, Some(_)) => return Ordering::Less,
|
|
(None, None) => return Ordering::Equal,
|
|
}
|
|
|
|
left_next!();
|
|
right_next!();
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn cmp(left: &[&str]) {
|
|
let mut right = left.to_vec();
|
|
right.sort_by(|a, b| natsort(a.as_bytes(), b.as_bytes(), true));
|
|
assert_eq!(left, right);
|
|
}
|
|
|
|
#[test]
|
|
fn test_natsort() {
|
|
let dates = ["1999-3-3", "1999-12-25", "2000-1-2", "2000-1-10", "2000-3-23"];
|
|
let fractions = [
|
|
"1.002.01", "1.002.03", "1.002.08", "1.009.02", "1.009.10", "1.009.20", "1.010.12",
|
|
"1.011.02",
|
|
];
|
|
let words = [
|
|
"1-02",
|
|
"1-2",
|
|
"1-20",
|
|
"10-20",
|
|
"fred",
|
|
"jane",
|
|
"pic01",
|
|
"pic02",
|
|
"pic02a",
|
|
"pic02000",
|
|
"pic05",
|
|
"pic2",
|
|
"pic3",
|
|
"pic4",
|
|
"pic 4 else",
|
|
"pic 5",
|
|
"pic 5 ",
|
|
"pic 5 something",
|
|
"pic 6",
|
|
"pic 7",
|
|
"pic100",
|
|
"pic100a",
|
|
"pic120",
|
|
"pic121",
|
|
"tom",
|
|
"x2-g8",
|
|
"x2-y08",
|
|
"x2-y7",
|
|
"x8-y8",
|
|
];
|
|
|
|
cmp(&dates);
|
|
cmp(&fractions);
|
|
cmp(&words);
|
|
}
|
|
}
|