@@ -2189,6 +2189,23 @@ fn push_basic_escape(buf: &mut String, byte: u8) {
21892189 }
21902190}
21912191
2192+ #[ derive( Debug ) ]
2193+ struct DirData {
2194+ dir_path : PathBuf ,
2195+ read_dir : ReadDir ,
2196+ needs_blank_line : bool ,
2197+ }
2198+
2199+ impl DirData {
2200+ fn new ( dir_path : PathBuf , read_dir : ReadDir , needs_blank_line : bool ) -> Self {
2201+ Self {
2202+ dir_path,
2203+ read_dir,
2204+ needs_blank_line,
2205+ }
2206+ }
2207+ }
2208+
21922209// A struct to encapsulate state that is passed around from `list` functions.
21932210struct ListState < ' a > {
21942211 out : BufWriter < Stdout > ,
@@ -2203,6 +2220,9 @@ struct ListState<'a> {
22032220 #[ cfg( unix) ]
22042221 gid_cache : FxHashMap < u32 , String > ,
22052222 recent_time_range : RangeInclusive < SystemTime > ,
2223+ stack : Vec < DirData > ,
2224+ listed_ancestors : FxHashSet < FileInformation > ,
2225+ initial_locs_len : usize ,
22062226}
22072227
22082228#[ allow( clippy:: cognitive_complexity) ]
@@ -2224,6 +2244,9 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
22242244 // According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average.
22252245 recent_time_range : ( SystemTime :: now ( ) - Duration :: new ( 31_556_952 / 2 , 0 ) )
22262246 ..=SystemTime :: now ( ) ,
2247+ stack : Vec :: with_capacity ( initial_locs_len) ,
2248+ listed_ancestors : FxHashSet :: default ( ) ,
2249+ initial_locs_len,
22272250 } ;
22282251
22292252 for loc in locs {
@@ -2267,7 +2290,10 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
22672290
22682291 display_items ( & files, config, & mut state, & mut dired) ?;
22692292
2293+ let mut buf: Vec < PathData > = Vec :: new ( ) ;
2294+
22702295 for ( pos, path_data) in dirs. iter ( ) . enumerate ( ) {
2296+ let needs_blank_line = pos != 0 || !files. is_empty ( ) ;
22712297 // Do read_dir call here to match GNU semantics by printing
22722298 // read_dir errors before directory headings, names and totals
22732299 let read_dir = match fs:: read_dir ( path_data. path ( ) ) {
@@ -2284,41 +2310,28 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
22842310 Ok ( rd) => rd,
22852311 } ;
22862312
2287- // Print dir heading - name... 'total' comes after error display
2288- if initial_locs_len > 1 || config. recursive {
2289- let needs_blank_line = !( pos. eq ( & 0usize ) && files. is_empty ( ) ) ;
2290- if needs_blank_line {
2291- writeln ! ( state. out) ?;
2292- if config. dired {
2293- dired. padding += 1 ;
2294- }
2295- }
2296- if config. dired {
2297- dired:: indent ( & mut state. out ) ?;
2298- }
2299- show_dir_name ( path_data, & mut state. out , config) ?;
2300- writeln ! ( state. out) ?;
2301- if config. dired {
2302- let dir_len = path_data. display_name ( ) . len ( ) ;
2303- // add the //SUBDIRED// coordinates
2304- dired:: calculate_subdired ( & mut dired, dir_len) ;
2305- // Add the padding for the dir name
2306- dired:: add_dir_name ( & mut dired, dir_len) ;
2307- }
2308- }
2309- let mut listed_ancestors = FxHashSet :: default ( ) ;
2310- listed_ancestors. insert ( FileInformation :: from_path (
2313+ state. listed_ancestors . insert ( FileInformation :: from_path (
23112314 path_data. path ( ) ,
23122315 path_data. must_dereference ,
23132316 ) ?) ;
2314- enter_directory (
2315- path_data,
2316- read_dir,
2317+
2318+ // List each of the arguments to ls first.
2319+ depth_first_list (
2320+ DirData :: new ( path_data. path ( ) . to_path_buf ( ) , read_dir, needs_blank_line) ,
23172321 config,
23182322 & mut state,
2319- & mut listed_ancestors ,
2323+ & mut buf ,
23202324 & mut dired,
2325+ true ,
23212326 ) ?;
2327+
2328+ // Only runs if it must list recursively
2329+ while let Some ( dir_data) = state. stack . pop ( ) {
2330+ depth_first_list ( dir_data, config, & mut state, & mut buf, & mut dired, false ) ?;
2331+ }
2332+
2333+ // No need to clear state.buf since [`enter_directory`] drains it.
2334+ state. listed_ancestors . clear ( ) ;
23222335 }
23232336 if config. dired && !config. hyperlink {
23242337 dired:: print_dired_output ( config, & dired, & mut state. out ) ?;
@@ -2435,18 +2448,60 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool {
24352448 . any ( |p| p. matches_with ( & file_name, options) )
24362449}
24372450
2438- #[ allow( clippy:: cognitive_complexity) ]
2439- fn enter_directory (
2440- path_data : & PathData ,
2441- mut read_dir : ReadDir ,
2451+ fn depth_first_list (
2452+ DirData {
2453+ dir_path,
2454+ mut read_dir,
2455+ needs_blank_line,
2456+ } : DirData ,
24422457 config : & Config ,
24432458 state : & mut ListState ,
2444- listed_ancestors : & mut FxHashSet < FileInformation > ,
2459+ buf : & mut Vec < PathData > ,
24452460 dired : & mut DiredOutput ,
2461+ is_top_level : bool ,
24462462) -> UResult < ( ) > {
2447- // Create vec of entries with initial dot files
2448- let mut entries: Vec < PathData > = if config. files == Files :: All {
2449- vec ! [
2463+ let path_data = PathData :: new ( dir_path, None , None , config, false ) ;
2464+
2465+ // Print dir heading - name... 'total' comes after error display
2466+ if state. initial_locs_len > 1 || config. recursive {
2467+ if is_top_level {
2468+ if needs_blank_line {
2469+ writeln ! ( state. out) ?;
2470+ if config. dired {
2471+ dired. padding += 1 ;
2472+ }
2473+ }
2474+ if config. dired {
2475+ dired:: indent ( & mut state. out ) ?;
2476+ }
2477+ show_dir_name ( & path_data, & mut state. out , config) ?;
2478+ writeln ! ( state. out) ?;
2479+ if config. dired {
2480+ let dir_len = path_data. path ( ) . as_os_str ( ) . len ( ) ;
2481+ // add the //SUBDIRED// coordinates
2482+ dired:: calculate_subdired ( dired, dir_len) ;
2483+ // Add the padding for the dir name
2484+ dired:: add_dir_name ( dired, dir_len) ;
2485+ }
2486+ } else {
2487+ writeln ! ( state. out) ?;
2488+ if config. dired {
2489+ dired. padding += 1 ;
2490+ dired:: indent ( & mut state. out ) ?;
2491+ let dir_name_size = path_data. path ( ) . as_os_str ( ) . len ( ) ;
2492+ dired:: calculate_subdired ( dired, dir_name_size) ;
2493+ dired:: add_dir_name ( dired, dir_name_size) ;
2494+ }
2495+ show_dir_name ( & path_data, & mut state. out , config) ?;
2496+ writeln ! ( state. out) ?;
2497+ }
2498+ }
2499+
2500+ buf. clear ( ) ;
2501+ // Append entries with initial dot files and record their existence
2502+ let trim = if config. files == Files :: All {
2503+ const DOT_DIRECTORIES : usize = 2 ;
2504+ buf. extend :: < [ _ ; DOT_DIRECTORIES ] > ( [
24502505 PathData :: new (
24512506 path_data. path ( ) . to_path_buf ( ) ,
24522507 None ,
@@ -2461,52 +2516,51 @@ fn enter_directory(
24612516 config,
24622517 false ,
24632518 ) ,
2464- ]
2519+ ] ) ;
2520+ DOT_DIRECTORIES
24652521 } else {
2466- vec ! [ ]
2522+ 0
24672523 } ;
24682524
24692525 // Convert those entries to the PathData struct
24702526 for raw_entry in read_dir. by_ref ( ) {
2471- let dir_entry = match raw_entry {
2472- Ok ( path) => path,
2527+ match raw_entry {
2528+ Ok ( dir_entry) => {
2529+ if should_display ( & dir_entry, config) {
2530+ buf. push ( PathData :: new (
2531+ dir_entry. path ( ) ,
2532+ Some ( dir_entry) ,
2533+ None ,
2534+ config,
2535+ false ,
2536+ ) ) ;
2537+ }
2538+ }
24732539 Err ( err) => {
24742540 state. out . flush ( ) ?;
24752541 show ! ( LsError :: IOError ( err) ) ;
2476- continue ;
24772542 }
2478- } ;
2479-
2480- if should_display ( & dir_entry, config) {
2481- let entry_path_data =
2482- PathData :: new ( dir_entry. path ( ) , Some ( dir_entry) , None , config, false ) ;
2483- entries. push ( entry_path_data) ;
24842543 }
24852544 }
24862545
2487- sort_entries ( & mut entries , config) ;
2546+ sort_entries ( buf , config) ;
24882547
2489- // Print total after any error display
24902548 if config. format == Format :: Long || config. alloc_size {
2491- let total = return_total ( & entries , config, & mut state. out ) ?;
2549+ let total = return_total ( buf , config, & mut state. out ) ?;
24922550 write ! ( state. out, "{}" , total. as_str( ) ) ?;
24932551 if config. dired {
24942552 dired:: add_total ( dired, total. len ( ) ) ;
24952553 }
24962554 }
24972555
2498- display_items ( & entries , config, state, dired) ?;
2556+ display_items ( buf , config, state, dired) ?;
24992557
25002558 if config. recursive {
2501- // release the open fd before recursing to not run out of resources
2502- for entry in & entries {
2503- entry. de . take ( ) ;
2504- }
2505- drop ( read_dir) ;
2506- for e in entries
2559+ for e in buf
25072560 . iter ( )
2508- . skip ( if config . files == Files :: All { 2 } else { 0 } )
2561+ . skip ( trim )
25092562 . filter ( |p| p. file_type ( ) . is_some_and ( FileType :: is_dir) )
2563+ . rev ( )
25102564 {
25112565 match fs:: read_dir ( e. path ( ) ) {
25122566 Err ( err) => {
@@ -2518,29 +2572,11 @@ fn enter_directory(
25182572 ) ) ;
25192573 }
25202574 Ok ( rd) => {
2521- if listed_ancestors
2522- . insert ( FileInformation :: from_path ( e. path ( ) , e. must_dereference ) ?)
2523- {
2524- // when listing several directories in recursive mode, we show
2525- // "dirname:" at the beginning of the file list
2526- writeln ! ( state. out) ?;
2527- if config. dired {
2528- // We already injected the first dir
2529- // Continue with the others
2530- // blank line between directory sections
2531- dired. padding += 1 ;
2532- dired:: indent ( & mut state. out ) ?;
2533- let dir_name_size = e. path ( ) . as_os_str ( ) . len ( ) ;
2534- dired:: calculate_subdired ( dired, dir_name_size) ;
2535- // inject dir name
2536- dired:: add_dir_name ( dired, dir_name_size) ;
2537- }
2538-
2539- show_dir_name ( e, & mut state. out , config) ?;
2540- writeln ! ( state. out) ?;
2541- enter_directory ( e, rd, config, state, listed_ancestors, dired) ?;
2542- listed_ancestors
2543- . remove ( & FileInformation :: from_path ( e. path ( ) , e. must_dereference ) ?) ;
2575+ let fi = FileInformation :: from_path ( e. path ( ) , e. must_dereference ) ?;
2576+ if state. listed_ancestors . insert ( fi) {
2577+ state
2578+ . stack
2579+ . push ( DirData :: new ( e. path ( ) . to_path_buf ( ) , rd, true ) ) ;
25442580 } else {
25452581 state. out . flush ( ) ?;
25462582 show ! ( LsError :: AlreadyListedError ( e. path( ) . to_path_buf( ) ) ) ;
@@ -2549,7 +2585,6 @@ fn enter_directory(
25492585 }
25502586 }
25512587 }
2552-
25532588 Ok ( ( ) )
25542589}
25552590
0 commit comments