project search: make sorting comparator comply with Ord preconditions (#17604)
Closes #17493 /cc @SomeoneToIgnore /cc @ConradIrwin Release Notes: - N/A
This commit is contained in:
parent
2fc74a1b71
commit
56bc3c36ad
3 changed files with 65 additions and 63 deletions
|
@ -3519,9 +3519,9 @@ mod tests {
|
||||||
" > .git",
|
" > .git",
|
||||||
" > a",
|
" > a",
|
||||||
" v b",
|
" v b",
|
||||||
" > [EDITOR: ''] <== selected",
|
|
||||||
" > 3",
|
" > 3",
|
||||||
" > 4",
|
" > 4",
|
||||||
|
" > [EDITOR: ''] <== selected",
|
||||||
" a-different-filename.tar.gz",
|
" a-different-filename.tar.gz",
|
||||||
" > C",
|
" > C",
|
||||||
" .dockerignore",
|
" .dockerignore",
|
||||||
|
@ -3542,10 +3542,10 @@ mod tests {
|
||||||
" > .git",
|
" > .git",
|
||||||
" > a",
|
" > a",
|
||||||
" v b",
|
" v b",
|
||||||
" > [PROCESSING: 'new-dir']",
|
" > 3",
|
||||||
" > 3 <== selected",
|
|
||||||
" > 4",
|
" > 4",
|
||||||
" a-different-filename.tar.gz",
|
" > [PROCESSING: 'new-dir']",
|
||||||
|
" a-different-filename.tar.gz <== selected",
|
||||||
" > C",
|
" > C",
|
||||||
" .dockerignore",
|
" .dockerignore",
|
||||||
]
|
]
|
||||||
|
@ -3559,10 +3559,10 @@ mod tests {
|
||||||
" > .git",
|
" > .git",
|
||||||
" > a",
|
" > a",
|
||||||
" v b",
|
" v b",
|
||||||
" > 3 <== selected",
|
" > 3",
|
||||||
" > 4",
|
" > 4",
|
||||||
" > new-dir",
|
" > new-dir",
|
||||||
" a-different-filename.tar.gz",
|
" a-different-filename.tar.gz <== selected",
|
||||||
" > C",
|
" > C",
|
||||||
" .dockerignore",
|
" .dockerignore",
|
||||||
]
|
]
|
||||||
|
@ -3576,10 +3576,10 @@ mod tests {
|
||||||
" > .git",
|
" > .git",
|
||||||
" > a",
|
" > a",
|
||||||
" v b",
|
" v b",
|
||||||
" > [EDITOR: '3'] <== selected",
|
" > 3",
|
||||||
" > 4",
|
" > 4",
|
||||||
" > new-dir",
|
" > new-dir",
|
||||||
" a-different-filename.tar.gz",
|
" [EDITOR: 'a-different-filename.tar.gz'] <== selected",
|
||||||
" > C",
|
" > C",
|
||||||
" .dockerignore",
|
" .dockerignore",
|
||||||
]
|
]
|
||||||
|
@ -3594,10 +3594,10 @@ mod tests {
|
||||||
" > .git",
|
" > .git",
|
||||||
" > a",
|
" > a",
|
||||||
" v b",
|
" v b",
|
||||||
" > 3 <== selected",
|
" > 3",
|
||||||
" > 4",
|
" > 4",
|
||||||
" > new-dir",
|
" > new-dir",
|
||||||
" a-different-filename.tar.gz",
|
" a-different-filename.tar.gz <== selected",
|
||||||
" > C",
|
" > C",
|
||||||
" .dockerignore",
|
" .dockerignore",
|
||||||
]
|
]
|
||||||
|
@ -3844,8 +3844,8 @@ mod tests {
|
||||||
&[
|
&[
|
||||||
//
|
//
|
||||||
"v root1",
|
"v root1",
|
||||||
" one.two.txt <== selected",
|
" one.txt <== selected",
|
||||||
" one.txt",
|
" one.two.txt",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3862,9 +3862,9 @@ mod tests {
|
||||||
&[
|
&[
|
||||||
//
|
//
|
||||||
"v root1",
|
"v root1",
|
||||||
" one.two copy.txt <== selected",
|
|
||||||
" one.two.txt",
|
|
||||||
" one.txt",
|
" one.txt",
|
||||||
|
" one copy.txt <== selected",
|
||||||
|
" one.two.txt",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3878,10 +3878,10 @@ mod tests {
|
||||||
&[
|
&[
|
||||||
//
|
//
|
||||||
"v root1",
|
"v root1",
|
||||||
" one.two copy 1.txt <== selected",
|
|
||||||
" one.two copy.txt",
|
|
||||||
" one.two.txt",
|
|
||||||
" one.txt",
|
" one.txt",
|
||||||
|
" one copy.txt",
|
||||||
|
" one copy 1.txt <== selected",
|
||||||
|
" one.two.txt",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4074,8 +4074,8 @@ mod tests {
|
||||||
" > b",
|
" > b",
|
||||||
" four.txt",
|
" four.txt",
|
||||||
" one.txt",
|
" one.txt",
|
||||||
" three copy.txt <== selected",
|
|
||||||
" three.txt",
|
" three.txt",
|
||||||
|
" three copy.txt <== selected",
|
||||||
" two.txt",
|
" two.txt",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -4105,8 +4105,8 @@ mod tests {
|
||||||
" > b",
|
" > b",
|
||||||
" four.txt",
|
" four.txt",
|
||||||
" one.txt",
|
" one.txt",
|
||||||
" three copy.txt",
|
|
||||||
" three.txt",
|
" three.txt",
|
||||||
|
" three copy.txt",
|
||||||
" two.txt",
|
" two.txt",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,9 +9,8 @@ use std::{
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use unicase::UniCase;
|
|
||||||
|
|
||||||
use crate::{maybe, NumericPrefixWithSuffix};
|
use crate::NumericPrefixWithSuffix;
|
||||||
|
|
||||||
/// Returns the path to the user's home directory.
|
/// Returns the path to the user's home directory.
|
||||||
pub fn home_dir() -> &'static PathBuf {
|
pub fn home_dir() -> &'static PathBuf {
|
||||||
|
@ -282,34 +281,29 @@ pub fn compare_paths(
|
||||||
let a_is_file = components_a.peek().is_none() && a_is_file;
|
let a_is_file = components_a.peek().is_none() && a_is_file;
|
||||||
let b_is_file = components_b.peek().is_none() && b_is_file;
|
let b_is_file = components_b.peek().is_none() && b_is_file;
|
||||||
let ordering = a_is_file.cmp(&b_is_file).then_with(|| {
|
let ordering = a_is_file.cmp(&b_is_file).then_with(|| {
|
||||||
let maybe_numeric_ordering = maybe!({
|
let path_a = Path::new(component_a.as_os_str());
|
||||||
let path_a = Path::new(component_a.as_os_str());
|
let num_and_remainder_a = NumericPrefixWithSuffix::from_numeric_prefixed_str(
|
||||||
let num_and_remainder_a = if a_is_file {
|
if a_is_file {
|
||||||
path_a.file_stem()
|
path_a.file_stem()
|
||||||
} else {
|
} else {
|
||||||
path_a.file_name()
|
path_a.file_name()
|
||||||
}
|
}
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
.and_then(NumericPrefixWithSuffix::from_numeric_prefixed_str)?;
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
let path_b = Path::new(component_b.as_os_str());
|
let path_b = Path::new(component_b.as_os_str());
|
||||||
let num_and_remainder_b = if b_is_file {
|
let num_and_remainder_b = NumericPrefixWithSuffix::from_numeric_prefixed_str(
|
||||||
|
if b_is_file {
|
||||||
path_b.file_stem()
|
path_b.file_stem()
|
||||||
} else {
|
} else {
|
||||||
path_b.file_name()
|
path_b.file_name()
|
||||||
}
|
}
|
||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
.and_then(NumericPrefixWithSuffix::from_numeric_prefixed_str)?;
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
num_and_remainder_a.partial_cmp(&num_and_remainder_b)
|
num_and_remainder_a.cmp(&num_and_remainder_b)
|
||||||
});
|
|
||||||
|
|
||||||
maybe_numeric_ordering.unwrap_or_else(|| {
|
|
||||||
let name_a = UniCase::new(component_a.as_os_str().to_string_lossy());
|
|
||||||
let name_b = UniCase::new(component_b.as_os_str().to_string_lossy());
|
|
||||||
|
|
||||||
name_a.cmp(&name_b)
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
if !ordering.is_eq() {
|
if !ordering.is_eq() {
|
||||||
return ordering;
|
return ordering;
|
||||||
|
@ -350,6 +344,18 @@ mod tests {
|
||||||
(Path::new("test_dirs/1.46/bar_2"), true),
|
(Path::new("test_dirs/1.46/bar_2"), true),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
let mut paths = vec![
|
||||||
|
(Path::new("root1/one.txt"), true),
|
||||||
|
(Path::new("root1/one.two.txt"), true),
|
||||||
|
];
|
||||||
|
paths.sort_by(|&a, &b| compare_paths(a, b));
|
||||||
|
assert_eq!(
|
||||||
|
paths,
|
||||||
|
vec![
|
||||||
|
(Path::new("root1/one.txt"), true),
|
||||||
|
(Path::new("root1/one.two.txt"), true),
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -644,27 +644,27 @@ impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
|
||||||
/// This is useful for turning regular alphanumerically sorted sequences as `1-abc, 10, 11-def, .., 2, 21-abc`
|
/// This is useful for turning regular alphanumerically sorted sequences as `1-abc, 10, 11-def, .., 2, 21-abc`
|
||||||
/// into `1-abc, 2, 10, 11-def, .., 21-abc`
|
/// into `1-abc, 2, 10, 11-def, .., 21-abc`
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct NumericPrefixWithSuffix<'a>(i32, &'a str);
|
pub struct NumericPrefixWithSuffix<'a>(Option<u32>, &'a str);
|
||||||
|
|
||||||
impl<'a> NumericPrefixWithSuffix<'a> {
|
impl<'a> NumericPrefixWithSuffix<'a> {
|
||||||
pub fn from_numeric_prefixed_str(str: &'a str) -> Option<Self> {
|
pub fn from_numeric_prefixed_str(str: &'a str) -> Self {
|
||||||
let i = str.chars().take_while(|c| c.is_ascii_digit()).count();
|
let i = str.chars().take_while(|c| c.is_ascii_digit()).count();
|
||||||
let (prefix, remainder) = str.split_at(i);
|
let (prefix, remainder) = str.split_at(i);
|
||||||
|
|
||||||
match prefix.parse::<i32>() {
|
let prefix = prefix.parse().ok();
|
||||||
Ok(prefix) => Some(NumericPrefixWithSuffix(prefix, remainder)),
|
Self(prefix, remainder)
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ord for NumericPrefixWithSuffix<'_> {
|
impl Ord for NumericPrefixWithSuffix<'_> {
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
let NumericPrefixWithSuffix(num_a, remainder_a) = self;
|
match (self.0, other.0) {
|
||||||
let NumericPrefixWithSuffix(num_b, remainder_b) = other;
|
(None, None) => UniCase::new(self.1).cmp(&UniCase::new(other.1)),
|
||||||
num_a
|
(None, Some(_)) => Ordering::Greater,
|
||||||
.cmp(num_b)
|
(Some(_), None) => Ordering::Less,
|
||||||
.then_with(|| UniCase::new(remainder_a).cmp(&UniCase::new(remainder_b)))
|
(Some(a), Some(b)) => a
|
||||||
|
.cmp(&b)
|
||||||
|
.then_with(|| UniCase::new(self.1).cmp(&UniCase::new(other.1))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -737,66 +737,62 @@ mod tests {
|
||||||
let target = "1a";
|
let target = "1a";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
||||||
Some(NumericPrefixWithSuffix(1, "a"))
|
NumericPrefixWithSuffix(Some(1), "a")
|
||||||
);
|
);
|
||||||
|
|
||||||
let target = "12ab";
|
let target = "12ab";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
||||||
Some(NumericPrefixWithSuffix(12, "ab"))
|
NumericPrefixWithSuffix(Some(12), "ab")
|
||||||
);
|
);
|
||||||
|
|
||||||
let target = "12_ab";
|
let target = "12_ab";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
||||||
Some(NumericPrefixWithSuffix(12, "_ab"))
|
NumericPrefixWithSuffix(Some(12), "_ab")
|
||||||
);
|
);
|
||||||
|
|
||||||
let target = "1_2ab";
|
let target = "1_2ab";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
||||||
Some(NumericPrefixWithSuffix(1, "_2ab"))
|
NumericPrefixWithSuffix(Some(1), "_2ab")
|
||||||
);
|
);
|
||||||
|
|
||||||
let target = "1.2";
|
let target = "1.2";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
||||||
Some(NumericPrefixWithSuffix(1, ".2"))
|
NumericPrefixWithSuffix(Some(1), ".2")
|
||||||
);
|
);
|
||||||
|
|
||||||
let target = "1.2_a";
|
let target = "1.2_a";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
||||||
Some(NumericPrefixWithSuffix(1, ".2_a"))
|
NumericPrefixWithSuffix(Some(1), ".2_a")
|
||||||
);
|
);
|
||||||
|
|
||||||
let target = "12.2_a";
|
let target = "12.2_a";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
||||||
Some(NumericPrefixWithSuffix(12, ".2_a"))
|
NumericPrefixWithSuffix(Some(12), ".2_a")
|
||||||
);
|
);
|
||||||
|
|
||||||
let target = "12a.2_a";
|
let target = "12a.2_a";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
NumericPrefixWithSuffix::from_numeric_prefixed_str(target),
|
||||||
Some(NumericPrefixWithSuffix(12, "a.2_a"))
|
NumericPrefixWithSuffix(Some(12), "a.2_a")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_numeric_prefix_with_suffix() {
|
fn test_numeric_prefix_with_suffix() {
|
||||||
let mut sorted = vec!["1-abc", "10", "11def", "2", "21-abc"];
|
let mut sorted = vec!["1-abc", "10", "11def", "2", "21-abc"];
|
||||||
sorted.sort_by_key(|s| {
|
sorted.sort_by_key(|s| NumericPrefixWithSuffix::from_numeric_prefixed_str(s));
|
||||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(s).unwrap_or_else(|| {
|
|
||||||
panic!("Cannot convert string `{s}` into NumericPrefixWithSuffix")
|
|
||||||
})
|
|
||||||
});
|
|
||||||
assert_eq!(sorted, ["1-abc", "2", "10", "11def", "21-abc"]);
|
assert_eq!(sorted, ["1-abc", "2", "10", "11def", "21-abc"]);
|
||||||
|
|
||||||
for numeric_prefix_less in ["numeric_prefix_less", "aaa", "~™£"] {
|
for numeric_prefix_less in ["numeric_prefix_less", "aaa", "~™£"] {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
NumericPrefixWithSuffix::from_numeric_prefixed_str(numeric_prefix_less),
|
NumericPrefixWithSuffix::from_numeric_prefixed_str(numeric_prefix_less),
|
||||||
None,
|
NumericPrefixWithSuffix(None, numeric_prefix_less),
|
||||||
"String without numeric prefix `{numeric_prefix_less}` should not be converted into NumericPrefixWithSuffix"
|
"String without numeric prefix `{numeric_prefix_less}` should not be converted into NumericPrefixWithSuffix"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue