mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-19 09:22:12 +00:00
fix sorting
This commit is contained in:
1 parent
c84e1c9a06
commit
128338aa05
1 file changed
+79
-1
+79
-1
@@ -25,9 +25,12 @@
|
||||
//! edits is read again, a file with edits is flagged `replaced` and the user chooses when saving it
|
||||
//! whether to overwrite the disk or to read it again (`reload`).
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs;
|
||||
use std::iter::Peekable;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::Chars;
|
||||
|
||||
use crate::Engine;
|
||||
use gpx_engine::{ExportOptions, FileId, api};
|
||||
@@ -137,6 +140,41 @@ fn is_gpx(path: &Path) -> bool {
|
||||
.is_some_and(|extension| extension.eq_ignore_ascii_case("gpx"))
|
||||
}
|
||||
|
||||
/// Compares names the way people read them: the numbers in them by their value ("track 2" before
|
||||
/// "track 10"), the rest regardless of case.
|
||||
fn natural_cmp(a: &str, b: &str) -> Ordering {
|
||||
let (mut a, mut b) = (a.chars().peekable(), b.chars().peekable());
|
||||
loop {
|
||||
let order = match (a.peek().copied(), b.peek().copied()) {
|
||||
(None, None) => return Ordering::Equal,
|
||||
(None, Some(_)) => return Ordering::Less,
|
||||
(Some(_), None) => return Ordering::Greater,
|
||||
(Some(x), Some(y)) if x.is_ascii_digit() && y.is_ascii_digit() => {
|
||||
let (x, y) = (digits(&mut a), digits(&mut b));
|
||||
let (x, y) = (x.trim_start_matches('0'), y.trim_start_matches('0'));
|
||||
x.len().cmp(&y.len()).then_with(|| x.cmp(y))
|
||||
}
|
||||
(Some(x), Some(y)) => {
|
||||
a.next();
|
||||
b.next();
|
||||
x.to_lowercase().cmp(y.to_lowercase())
|
||||
}
|
||||
};
|
||||
if order != Ordering::Equal {
|
||||
return order;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The digits at the beginning of `chars`, which are taken.
|
||||
fn digits(chars: &mut Peekable<Chars>) -> String {
|
||||
let mut digits = String::new();
|
||||
while let Some(digit) = chars.next_if(char::is_ascii_digit) {
|
||||
digits.push(digit);
|
||||
}
|
||||
digits
|
||||
}
|
||||
|
||||
pub fn canonical(path: &Path) -> PathBuf {
|
||||
fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
|
||||
}
|
||||
@@ -712,10 +750,18 @@ impl Workspace {
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
// the files without their extension, so that a name that begins another comes first
|
||||
let sorted_name = |entry: &Entry| {
|
||||
if entry.is_dir {
|
||||
entry.name.clone()
|
||||
} else {
|
||||
stem(Path::new(&entry.name))
|
||||
}
|
||||
};
|
||||
entries.sort_by(|a, b| {
|
||||
b.is_dir
|
||||
.cmp(&a.is_dir)
|
||||
.then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase()))
|
||||
.then_with(|| natural_cmp(&sorted_name(a), &sorted_name(b)))
|
||||
.then_with(|| a.name.cmp(&b.name))
|
||||
});
|
||||
Ok(entries)
|
||||
@@ -956,6 +1002,8 @@ mod tests {
|
||||
let mut fx = Fixture::new();
|
||||
fx.write("b.gpx", "b");
|
||||
fx.write("A.GPX", "a");
|
||||
fx.write("b 10.gpx", "b 10");
|
||||
fx.write("b 2.gpx", "b 2");
|
||||
fx.write("notes.txt", "not a track");
|
||||
fx.write(".hidden.gpx", "hidden");
|
||||
fs::create_dir(fx.folder.join("zoo")).unwrap();
|
||||
@@ -975,6 +1023,8 @@ mod tests {
|
||||
("zoo".to_string(), true),
|
||||
("A.GPX".to_string(), false),
|
||||
("b.gpx".to_string(), false),
|
||||
("b 2.gpx".to_string(), false),
|
||||
("b 10.gpx".to_string(), false),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -986,6 +1036,34 @@ mod tests {
|
||||
assert!(fx.workspace.list_folder(&elsewhere).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn names_are_sorted_by_the_numbers_they_hold() {
|
||||
let mut names = vec![
|
||||
"track 10",
|
||||
"track 2",
|
||||
"Track 1",
|
||||
"track",
|
||||
"track 02b",
|
||||
"track 2a",
|
||||
"b",
|
||||
"A",
|
||||
];
|
||||
names.sort_by(|a, b| natural_cmp(a, b).then_with(|| a.cmp(b)));
|
||||
assert_eq!(
|
||||
names,
|
||||
[
|
||||
"A",
|
||||
"b",
|
||||
"track",
|
||||
"Track 1",
|
||||
"track 2",
|
||||
"track 2a",
|
||||
"track 02b",
|
||||
"track 10",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_without_edits_is_read_again_when_the_disk_changes() {
|
||||
let mut fx = Fixture::new();
|
||||
|
||||
Reference in new issue
Block a user