@@ -13,7 +13,9 @@ use crate::{
1313#[ derive( Clone , Debug ) ]
1414pub enum SortField {
1515 Path ,
16+ PathNatural ,
1617 Basename ,
18+ BasenameNatural ,
1719 EdgeType ,
1820 Implied ,
1921 Neighbour ( String ) ,
@@ -25,7 +27,9 @@ impl FromStr for SortField {
2527 fn from_str ( s : & str ) -> Result < Self > {
2628 match s {
2729 "path" => Ok ( SortField :: Path ) ,
30+ "path_natural" => Ok ( SortField :: PathNatural ) ,
2831 "basename" => Ok ( SortField :: Basename ) ,
32+ "basename_natural" => Ok ( SortField :: BasenameNatural ) ,
2933 "field" => Ok ( SortField :: EdgeType ) ,
3034 "explicit" => Ok ( SortField :: Implied ) ,
3135 s if s. starts_with ( "neighbour-field:" ) => Ok ( SortField :: Neighbour (
@@ -122,7 +126,9 @@ impl EdgeSorter {
122126 fn get_edge_comparer < ' a > ( & self , graph : & ' a NoteGraph ) -> Comparer < ' a > {
123127 match self . field . clone ( ) {
124128 SortField :: Path => PathComparer . into ( ) ,
129+ SortField :: PathNatural => PathNaturalComparer . into ( ) ,
125130 SortField :: Basename => BasenameComparer . into ( ) ,
131+ SortField :: BasenameNatural => BasenameNaturalComparer . into ( ) ,
126132 SortField :: EdgeType => EdgeTypeComparer . into ( ) ,
127133 SortField :: Implied => ImpliedComparer . into ( ) ,
128134 SortField :: Neighbour ( neighbour_field) => {
@@ -165,12 +171,56 @@ pub trait EdgeComparer {
165171#[ enum_dispatch( EdgeComparer ) ]
166172pub enum Comparer < ' a > {
167173 PathComparer ,
174+ PathNaturalComparer ,
168175 BasenameComparer ,
176+ BasenameNaturalComparer ,
169177 EdgeTypeComparer ,
170178 ImpliedComparer ,
171179 NeighbourOrdering ( NeighbourComparer < ' a > ) ,
172180}
173181
182+ /// Compare two strings using natural sort order: numeric segments are compared
183+ /// numerically so that "note 2" < "note 10" rather than "note 10" < "note 2".
184+ fn natural_cmp ( a : & str , b : & str ) -> std:: cmp:: Ordering {
185+ let mut a_chars = a. chars ( ) . peekable ( ) ;
186+ let mut b_chars = b. chars ( ) . peekable ( ) ;
187+
188+ loop {
189+ let a_peek = a_chars. peek ( ) . copied ( ) ;
190+ let b_peek = b_chars. peek ( ) . copied ( ) ;
191+
192+ match ( a_peek, b_peek) {
193+ ( None , None ) => return std:: cmp:: Ordering :: Equal ,
194+ ( None , _) => return std:: cmp:: Ordering :: Less ,
195+ ( _, None ) => return std:: cmp:: Ordering :: Greater ,
196+ ( Some ( a_ch) , Some ( b_ch) ) if a_ch. is_ascii_digit ( ) && b_ch. is_ascii_digit ( ) => {
197+ let mut a_num = 0u64 ;
198+ while a_chars. peek ( ) . map_or ( false , |c| c. is_ascii_digit ( ) ) {
199+ a_num = a_num * 10 + ( a_chars. next ( ) . unwrap ( ) as u64 - '0' as u64 ) ;
200+ }
201+ let mut b_num = 0u64 ;
202+ while b_chars. peek ( ) . map_or ( false , |c| c. is_ascii_digit ( ) ) {
203+ b_num = b_num * 10 + ( b_chars. next ( ) . unwrap ( ) as u64 - '0' as u64 ) ;
204+ }
205+ match a_num. cmp ( & b_num) {
206+ std:: cmp:: Ordering :: Equal => continue ,
207+ other => return other,
208+ }
209+ }
210+ ( Some ( a_ch) , Some ( b_ch) ) => {
211+ match a_ch. cmp ( & b_ch) {
212+ std:: cmp:: Ordering :: Equal => {
213+ a_chars. next ( ) ;
214+ b_chars. next ( ) ;
215+ continue ;
216+ }
217+ other => return other,
218+ }
219+ }
220+ }
221+ }
222+ }
223+
174224#[ derive( Default ) ]
175225pub struct PathComparer ;
176226
@@ -196,6 +246,32 @@ impl EdgeComparer for BasenameComparer {
196246 }
197247}
198248
249+ #[ derive( Default ) ]
250+ pub struct PathNaturalComparer ;
251+
252+ impl EdgeComparer for PathNaturalComparer {
253+ fn compare ( & self , graph : & NoteGraph , a : & EdgeStruct , b : & EdgeStruct ) -> std:: cmp:: Ordering {
254+ natural_cmp (
255+ a. target_path_ref ( graph) . unwrap ( ) ,
256+ b. target_path_ref ( graph) . unwrap ( ) ,
257+ )
258+ }
259+ }
260+
261+ #[ derive( Default ) ]
262+ pub struct BasenameNaturalComparer ;
263+
264+ impl EdgeComparer for BasenameNaturalComparer {
265+ fn compare ( & self , graph : & NoteGraph , a : & EdgeStruct , b : & EdgeStruct ) -> std:: cmp:: Ordering {
266+ let a_target = a. target_path_ref ( graph) . unwrap ( ) ;
267+ let b_target = b. target_path_ref ( graph) . unwrap ( ) ;
268+ let a_basename = a_target. split ( '/' ) . next_back ( ) . unwrap ( ) ;
269+ let b_basename = b_target. split ( '/' ) . next_back ( ) . unwrap ( ) ;
270+
271+ natural_cmp ( a_basename, b_basename)
272+ }
273+ }
274+
199275#[ derive( Default ) ]
200276pub struct EdgeTypeComparer ;
201277
0 commit comments