@@ -2189,6 +2189,8 @@ fn push_basic_escape(buf: &mut String, byte: u8) {
21892189 }
21902190}
21912191
2192+ type DirData = ( PathBuf , bool ) ;
2193+
21922194// A struct to encapsulate state that is passed around from `list` functions.
21932195struct ListState < ' a > {
21942196 out : BufWriter < Stdout > ,
@@ -2203,6 +2205,9 @@ struct ListState<'a> {
22032205 #[ cfg( unix) ]
22042206 gid_cache : FxHashMap < u32 , String > ,
22052207 recent_time_range : RangeInclusive < SystemTime > ,
2208+ stack : Vec < DirData > ,
2209+ listed_ancestors : FxHashSet < FileInformation > ,
2210+ initial_locs_len : usize ,
22062211}
22072212
22082213#[ allow( clippy:: cognitive_complexity) ]
@@ -2224,6 +2229,9 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
22242229 // According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average.
22252230 recent_time_range : ( SystemTime :: now ( ) - Duration :: new ( 31_556_952 / 2 , 0 ) )
22262231 ..=SystemTime :: now ( ) ,
2232+ stack : Vec :: new ( ) ,
2233+ listed_ancestors : FxHashSet :: default ( ) ,
2234+ initial_locs_len,
22272235 } ;
22282236
22292237 for loc in locs {
@@ -2267,7 +2275,10 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
22672275
22682276 display_items ( & files, config, & mut state, & mut dired) ?;
22692277
2278+ let mut buf: Vec < PathData > = Vec :: new ( ) ;
2279+
22702280 for ( pos, path_data) in dirs. iter ( ) . enumerate ( ) {
2281+ let needs_blank_line = pos != 0 || !files. is_empty ( ) ;
22712282 // Do read_dir call here to match GNU semantics by printing
22722283 // read_dir errors before directory headings, names and totals
22732284 let read_dir = match fs:: read_dir ( path_data. path ( ) ) {
@@ -2284,41 +2295,44 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
22842295 Ok ( rd) => rd,
22852296 } ;
22862297
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 (
2298+ state. listed_ancestors . insert ( FileInformation :: from_path (
23112299 path_data. path ( ) ,
23122300 path_data. must_dereference ,
23132301 ) ?) ;
2314- enter_directory (
2315- path_data,
2302+
2303+ // List each of the arguments to ls first.
2304+ depth_first_list (
2305+ ( path_data. path ( ) . to_path_buf ( ) , needs_blank_line) ,
23162306 read_dir,
23172307 config,
23182308 & mut state,
2319- & mut listed_ancestors ,
2309+ & mut buf ,
23202310 & mut dired,
2311+ true ,
23212312 ) ?;
2313+
2314+ // Only runs if it must list recursively.
2315+ while let Some ( dir_data) = state. stack . pop ( ) {
2316+ let read_dir = match fs:: read_dir ( & dir_data. 0 ) {
2317+ Err ( err) => {
2318+ // flush stdout buffer before the error to preserve formatting and order
2319+ state. out . flush ( ) ?;
2320+ show ! ( LsError :: IOErrorContext (
2321+ path_data. path( ) . to_path_buf( ) ,
2322+ err,
2323+ path_data. command_line
2324+ ) ) ;
2325+ continue ;
2326+ }
2327+ Ok ( rd) => rd,
2328+ } ;
2329+ depth_first_list (
2330+ dir_data, read_dir, config, & mut state, & mut buf, & mut dired, false ,
2331+ ) ?;
2332+ }
2333+
2334+ // No need to clear state.buf since [`enter_directory`] drains it.
2335+ state. listed_ancestors . clear ( ) ;
23222336 }
23232337 if config. dired && !config. hyperlink {
23242338 dired:: print_dired_output ( config, & dired, & mut state. out ) ?;
@@ -2435,18 +2449,57 @@ fn should_display(entry: &DirEntry, config: &Config) -> bool {
24352449 . any ( |p| p. matches_with ( & file_name, options) )
24362450}
24372451
2438- #[ allow( clippy:: cognitive_complexity) ]
2439- fn enter_directory (
2440- path_data : & PathData ,
2452+ fn depth_first_list (
2453+ ( dir_path, needs_blank_line) : DirData ,
24412454 mut read_dir : ReadDir ,
24422455 config : & Config ,
24432456 state : & mut ListState ,
2444- listed_ancestors : & mut FxHashSet < FileInformation > ,
2457+ buf : & mut Vec < PathData > ,
24452458 dired : & mut DiredOutput ,
2459+ is_top_level : bool ,
24462460) -> UResult < ( ) > {
2447- // Create vec of entries with initial dot files
2448- let mut entries: Vec < PathData > = if config. files == Files :: All {
2449- vec ! [
2461+ let path_data = PathData :: new ( dir_path, None , None , config, false ) ;
2462+
2463+ // Print dir heading - name... 'total' comes after error display
2464+ if state. initial_locs_len > 1 || config. recursive {
2465+ if is_top_level {
2466+ if needs_blank_line {
2467+ writeln ! ( state. out) ?;
2468+ if config. dired {
2469+ dired. padding += 1 ;
2470+ }
2471+ }
2472+ if config. dired {
2473+ dired:: indent ( & mut state. out ) ?;
2474+ }
2475+ show_dir_name ( & path_data, & mut state. out , config) ?;
2476+ writeln ! ( state. out) ?;
2477+ if config. dired {
2478+ let dir_len = path_data. path ( ) . as_os_str ( ) . len ( ) ;
2479+ // add the //SUBDIRED// coordinates
2480+ dired:: calculate_subdired ( dired, dir_len) ;
2481+ // Add the padding for the dir name
2482+ dired:: add_dir_name ( dired, dir_len) ;
2483+ }
2484+ } else {
2485+ writeln ! ( state. out) ?;
2486+ if config. dired {
2487+ dired. padding += 1 ;
2488+ dired:: indent ( & mut state. out ) ?;
2489+ let dir_name_size = path_data. path ( ) . as_os_str ( ) . len ( ) ;
2490+ dired:: calculate_subdired ( dired, dir_name_size) ;
2491+ dired:: add_dir_name ( dired, dir_name_size) ;
2492+ }
2493+ show_dir_name ( & path_data, & mut state. out , config) ?;
2494+ writeln ! ( state. out) ?;
2495+ }
2496+ }
2497+
2498+ buf. clear ( ) ;
2499+ // Append entries with initial dot files and record their existence
2500+ let trim = if config. files == Files :: All {
2501+ const DOT_DIRECTORIES : usize = 2 ;
2502+ buf. extend :: < [ _ ; DOT_DIRECTORIES ] > ( [
24502503 PathData :: new (
24512504 path_data. path ( ) . to_path_buf ( ) ,
24522505 None ,
@@ -2461,95 +2514,61 @@ fn enter_directory(
24612514 config,
24622515 false ,
24632516 ) ,
2464- ]
2517+ ] ) ;
2518+ DOT_DIRECTORIES
24652519 } else {
2466- vec ! [ ]
2520+ 0
24672521 } ;
24682522
24692523 // Convert those entries to the PathData struct
24702524 for raw_entry in read_dir. by_ref ( ) {
2471- let dir_entry = match raw_entry {
2472- Ok ( path) => path,
2525+ match raw_entry {
2526+ Ok ( dir_entry) => {
2527+ if should_display ( & dir_entry, config) {
2528+ buf. push ( PathData :: new (
2529+ dir_entry. path ( ) ,
2530+ Some ( dir_entry) ,
2531+ None ,
2532+ config,
2533+ false ,
2534+ ) ) ;
2535+ }
2536+ }
24732537 Err ( err) => {
24742538 state. out . flush ( ) ?;
24752539 show ! ( LsError :: IOError ( err) ) ;
2476- continue ;
24772540 }
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) ;
24842541 }
24852542 }
24862543
2487- sort_entries ( & mut entries , config) ;
2544+ sort_entries ( buf , config) ;
24882545
2489- // Print total after any error display
24902546 if config. format == Format :: Long || config. alloc_size {
2491- let total = return_total ( & entries , config, & mut state. out ) ?;
2547+ let total = return_total ( buf , config, & mut state. out ) ?;
24922548 write ! ( state. out, "{}" , total. as_str( ) ) ?;
24932549 if config. dired {
24942550 dired:: add_total ( dired, total. len ( ) ) ;
24952551 }
24962552 }
24972553
2498- display_items ( & entries , config, state, dired) ?;
2554+ display_items ( buf , config, state, dired) ?;
24992555
25002556 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
2557+ for e in buf
25072558 . iter ( )
2508- . skip ( if config . files == Files :: All { 2 } else { 0 } )
2559+ . skip ( trim )
25092560 . filter ( |p| p. file_type ( ) . is_some_and ( FileType :: is_dir) )
2561+ . rev ( )
25102562 {
2511- match fs:: read_dir ( e. path ( ) ) {
2512- Err ( err) => {
2513- state. out . flush ( ) ?;
2514- show ! ( LsError :: IOErrorContext (
2515- e. path( ) . to_path_buf( ) ,
2516- err,
2517- e. command_line
2518- ) ) ;
2519- }
2520- 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 ) ?) ;
2544- } else {
2545- state. out . flush ( ) ?;
2546- show ! ( LsError :: AlreadyListedError ( e. path( ) . to_path_buf( ) ) ) ;
2547- }
2548- }
2563+ let fi = FileInformation :: from_path ( e. path ( ) , e. must_dereference ) ?;
2564+ if state. listed_ancestors . insert ( fi) {
2565+ state. stack . push ( ( e. path ( ) . to_path_buf ( ) , true ) ) ;
2566+ } else {
2567+ state. out . flush ( ) ?;
2568+ show ! ( LsError :: AlreadyListedError ( e. path( ) . to_path_buf( ) ) ) ;
25492569 }
25502570 }
25512571 }
2552-
25532572 Ok ( ( ) )
25542573}
25552574
0 commit comments