From ac31c824e4eeb0468c375002a117111c1c895235 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Tue, 27 Feb 2024 18:42:15 +0100 Subject: [PATCH] Use numeric sorting if possible in project panel (#8486) Previously, if you had the following files/folders in your project 1-abc 10 11-def ... 2 21-abc that's how we'd display them. With this change, we now try to parse them as numbers, if possible, and use that to sort. If we can't parse a component as a number, we fall back to normal string comparison. End result is this: 1-abc 2 10 11-def ... 21-abc Release Notes: - Fixed filenames with numeric components (`1.txt`, `1/one.txt`, ...) not being sorted as numbers, but as string. Before: ![screenshot-2024-02-27-18 29 43@2x](https://github.com/zed-industries/zed/assets/1185253/2d223126-329f-4ae7-9a12-d33e2c3fe52f) After: ![after](https://github.com/zed-industries/zed/assets/1185253/f4f98fa0-e66f-40aa-aa28-189143cbb75f) --------- Co-authored-by: Marshall --- crates/project_panel/src/project_panel.rs | 55 ++++++++++++++++++++--- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c19f293fdc..e531191a54 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1178,11 +1178,27 @@ impl ProjectPanel { let a_is_file = components_a.peek().is_none() && entry_a.is_file(); let b_is_file = components_b.peek().is_none() && entry_b.is_file(); let ordering = a_is_file.cmp(&b_is_file).then_with(|| { - 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) + let maybe_numeric_ordering = maybe!({ + let num_and_remainder_a = Path::new(component_a.as_os_str()) + .file_stem() + .and_then(|s| s.to_str()) + .and_then(NumericPrefixWithSuffix::from_str)?; + let num_and_remainder_b = Path::new(component_b.as_os_str()) + .file_stem() + .and_then(|s| s.to_str()) + .and_then(NumericPrefixWithSuffix::from_str)?; + + num_and_remainder_a.partial_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() { return ordering; @@ -1482,6 +1498,35 @@ impl ProjectPanel { } } +#[derive(Debug, PartialEq)] +struct NumericPrefixWithSuffix<'a>(i32, &'a str); + +impl<'a> NumericPrefixWithSuffix<'a> { + fn from_str(str: &'a str) -> Option { + 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::() { + Ok(prefix) => Some(NumericPrefixWithSuffix(prefix, remainder)), + Err(_) => None, + } + } +} + +impl<'a> PartialOrd for NumericPrefixWithSuffix<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + 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) -> impl IntoElement { let has_worktree = self.visible_entries.len() != 0;