Cargo.lock 🔗
@@ -10487,6 +10487,7 @@ dependencies = [
"take-until",
"tempfile",
"tendril",
+ "unicase",
"url",
]
Kirill Bulatov created
Cargo.lock | 1
Cargo.toml | 1
crates/project_panel/Cargo.toml | 2
crates/project_panel/src/project_panel.rs | 31 -------------
crates/util/Cargo.toml | 1
crates/util/src/util.rs | 57 +++++++++++++++++++++++++
6 files changed, 62 insertions(+), 31 deletions(-)
@@ -10487,6 +10487,7 @@ dependencies = [
"take-until",
"tempfile",
"tendril",
+ "unicase",
"url",
]
@@ -294,6 +294,7 @@ tree-sitter-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", r
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930" }
tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "0d08703e4c3f426ec61695d7617415fff97029bd" }
unindent = "0.1.7"
+unicase = "2.6"
url = "2.2"
uuid = { version = "1.1.2", features = ["v4"] }
wasmtime = "18.0"
@@ -26,7 +26,7 @@ serde_json.workspace = true
settings.workspace = true
theme.workspace = true
ui.workspace = true
-unicase = "2.6"
+unicase.workspace = true
util.workspace = true
client.workspace = true
workspace.workspace = true
@@ -27,7 +27,7 @@ use std::{cmp::Ordering, ffi::OsStr, ops::Range, path::Path, sync::Arc};
use theme::ThemeSettings;
use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem};
use unicase::UniCase;
-use util::{maybe, ResultExt, TryFutureExt};
+use util::{maybe, NumericPrefixWithSuffix, ResultExt, TryFutureExt};
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
notifications::DetachAndPromptErr,
@@ -1498,35 +1498,6 @@ impl ProjectPanel {
}
}
-#[derive(Debug, PartialEq)]
-struct NumericPrefixWithSuffix<'a>(i32, &'a str);
-
-impl<'a> NumericPrefixWithSuffix<'a> {
- fn from_str(str: &'a str) -> Option<Self> {
- let mut chars = str.chars();
- let prefix: String = chars.by_ref().take_while(|c| c.is_digit(10)).collect();
- let remainder = chars.as_str();
-
- match prefix.parse::<i32>() {
- Ok(prefix) => Some(NumericPrefixWithSuffix(prefix, remainder)),
- Err(_) => None,
- }
- }
-}
-
-impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- let NumericPrefixWithSuffix(num_a, remainder_a) = self;
- let NumericPrefixWithSuffix(num_b, remainder_b) = other;
-
- Some(
- num_a
- .cmp(&num_b)
- .then_with(|| UniCase::new(remainder_a).cmp(&UniCase::new(remainder_b))),
- )
- }
-}
-
impl Render for ProjectPanel {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
let has_worktree = self.visible_entries.len() != 0;
@@ -31,6 +31,7 @@ serde_json.workspace = true
smol.workspace = true
take-until = "0.2.0"
tempfile = { workspace = true, optional = true }
+unicase.workspace = true
url.workspace = true
[target.'cfg(windows)'.dependencies]
@@ -22,6 +22,7 @@ use std::{
task::{Context, Poll},
time::Instant,
};
+use unicase::UniCase;
pub use take_until::*;
@@ -487,6 +488,43 @@ impl<T: Ord + Clone> RangeExt<T> for RangeInclusive<T> {
}
}
+/// A way to sort strings with starting numbers numerically first, falling back to alphanumeric one,
+/// case-insensitive.
+///
+/// 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`
+#[derive(Debug, PartialEq, Eq)]
+pub struct NumericPrefixWithSuffix<'a>(i32, &'a str);
+
+impl<'a> NumericPrefixWithSuffix<'a> {
+ pub fn from_str(str: &'a str) -> Option<Self> {
+ let mut chars = str.chars();
+ let prefix: String = chars.by_ref().take_while(|c| c.is_digit(10)).collect();
+ let remainder = chars.as_str();
+
+ match prefix.parse::<i32>() {
+ Ok(prefix) => Some(NumericPrefixWithSuffix(prefix, remainder)),
+ Err(_) => None,
+ }
+ }
+}
+
+impl Ord for NumericPrefixWithSuffix<'_> {
+ fn cmp(&self, other: &Self) -> Ordering {
+ let NumericPrefixWithSuffix(num_a, remainder_a) = self;
+ let NumericPrefixWithSuffix(num_b, remainder_b) = other;
+ num_a
+ .cmp(&num_b)
+ .then_with(|| UniCase::new(remainder_a).cmp(&UniCase::new(remainder_b)))
+ }
+}
+
+impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -526,4 +564,23 @@ mod tests {
assert_eq!(truncate_and_trailoff("èèèèèè", 6), "èèèèèè");
assert_eq!(truncate_and_trailoff("èèèèèè", 5), "èèèèè…");
}
+
+ #[test]
+ fn test_numeric_prefix_with_suffix() {
+ let mut sorted = vec!["1-abc", "10", "11def", "2", "21-abc"];
+ sorted.sort_by_key(|s| {
+ NumericPrefixWithSuffix::from_str(s).unwrap_or_else(|| {
+ panic!("Cannot convert string `{s}` into NumericPrefixWithSuffix")
+ })
+ });
+ assert_eq!(sorted, ["1-abc", "2", "10", "11def", "21-abc"]);
+
+ for numeric_prefix_less in ["numeric_prefix_less", "aaa", "~™£"] {
+ assert_eq!(
+ NumericPrefixWithSuffix::from_str(numeric_prefix_less),
+ None,
+ "String without numeric prefix `{numeric_prefix_less}` should not be converted into NumericPrefixWithSuffix"
+ )
+ }
+ }
}