@@ -17,7 +17,7 @@ use pipe_trait::Pipe;
1717use serde:: Serialize ;
1818use std:: {
1919 collections:: HashMap ,
20- ffi:: OsStr ,
20+ ffi:: { OsStr , OsString } ,
2121 io:: stdout,
2222 iter:: once,
2323 path:: { Path , PathBuf } ,
@@ -133,7 +133,7 @@ where
133133 }
134134
135135 let min_ratio: f32 = min_ratio. into ( ) ;
136- let ( data_tree, deduplication_record) = {
136+ let ( mut data_tree, deduplication_record) = {
137137 let mut data_tree = data_tree;
138138 if min_ratio > 0.0 {
139139 data_tree. par_cull_insignificant_data ( min_ratio) ;
@@ -144,11 +144,41 @@ where
144144 let deduplication_record = hardlinks_handler. deduplicate ( & mut data_tree) ;
145145 if !only_one_arg {
146146 assert_eq ! ( data_tree. name( ) . as_os_str( ) . to_str( ) , Some ( "" ) ) ;
147- * data_tree. name_mut ( ) = OsStringDisplay :: os_string_from ( "(total)" ) ;
148147 }
149148 ( data_tree, deduplication_record)
150149 } ;
151150
151+ // Build the coloring map while the multi-arg root is still "" (a valid path prefix)
152+ // so that file_color receives real filesystem paths.
153+ let mut leaf_color_map: Option < HashMap < Vec < OsString > , Color > > = color. as_ref ( ) . map ( |_| {
154+ let mut map = HashMap :: new ( ) ;
155+ build_coloring_map ( & data_tree, & mut Vec :: new ( ) , & mut map) ;
156+ map
157+ } ) ;
158+
159+ // Rename the synthetic root and rekey the coloring map to match.
160+ if !only_one_arg {
161+ * data_tree. name_mut ( ) = OsStringDisplay :: os_string_from ( "(total)" ) ;
162+ if let Some ( map) = & mut leaf_color_map {
163+ let total = OsString :: from ( "(total)" ) ;
164+ let empty = OsString :: from ( "" ) ;
165+ * map = map
166+ . drain ( )
167+ . map ( |( mut key, color) | {
168+ if key. first ( ) == Some ( & empty) {
169+ key[ 0 ] = total. clone ( ) ;
170+ }
171+ ( key, color)
172+ } )
173+ . collect ( ) ;
174+ }
175+ }
176+
177+ let coloring: Option < Coloring > = color. map ( |ls_colors| {
178+ let map = leaf_color_map. take ( ) . unwrap ( ) ;
179+ Coloring :: new ( ls_colors, map)
180+ } ) ;
181+
152182 GLOBAL_STATUS_BOARD . clear_line ( 0 ) ;
153183
154184 if let Some ( json_output) = json_output {
@@ -198,24 +228,6 @@ where
198228 . or ( deduplication_result) ;
199229 }
200230
201- let coloring: Option < Coloring > = color. map ( |ls_colors| {
202- let mut map = HashMap :: new ( ) ;
203- if only_one_arg {
204- build_coloring_map ( & data_tree, & mut Vec :: new ( ) , & mut Vec :: new ( ) , & mut map) ;
205- } else {
206- // For multi-arg invocations the root is the synthetic "(total)" node.
207- // Include it in the map key (the visualizer's ancestor chain contains it)
208- // but skip it in the filesystem path (it doesn't exist on disk).
209- let root_name = data_tree. name ( ) . as_os_str ( ) ;
210- for child in data_tree. children ( ) {
211- let mut key_stack = vec ! [ root_name] ;
212- let mut fs_stack = Vec :: new ( ) ;
213- build_coloring_map ( child, & mut key_stack, & mut fs_stack, & mut map) ;
214- }
215- }
216- Coloring :: new ( ls_colors, map)
217- } ) ;
218-
219231 let visualizer = Visualizer {
220232 data_tree : & data_tree,
221233 bytes_format,
@@ -294,33 +306,27 @@ where
294306
295307/// Recursively walk a pruned [`DataTree`] and build a map of path-component vectors to [`Color`] values.
296308///
297- /// `key_stack` tracks the ancestor chain used as the HashMap key (must match what the
298- /// [`Visualizer`] constructs). `fs_path_stack` tracks the real filesystem path used for
299- /// file-type detection. These two stacks diverge when the root is a synthetic node like
300- /// `(total)` that has no corresponding directory on disk.
301- ///
309+ /// The `path_stack` argument is a reusable buffer of path components representing the current
310+ /// ancestor chain. Each recursive call pushes the node's name and pops it on return, so no
311+ /// cloning occurs during traversal — only at leaf insertions.
302312/// Leaf nodes (files or childless directories after pruning) are added to the map.
303313/// Nodes with children are skipped because the [`Visualizer`] uses the children count to
304314/// determine their color at render time.
305315fn build_coloring_map < ' a > (
306316 node : & ' a DataTree < OsStringDisplay , impl size:: Size > ,
307- key_stack : & mut Vec < & ' a OsStr > ,
308- fs_path_stack : & mut Vec < & ' a OsStr > ,
309- map : & mut HashMap < Vec < & ' a OsStr > , Color > ,
317+ path_stack : & mut Vec < & ' a OsStr > ,
318+ map : & mut HashMap < Vec < OsString > , Color > ,
310319) {
311- let name = node. name ( ) . as_os_str ( ) ;
312- key_stack. push ( name) ;
313- fs_path_stack. push ( name) ;
320+ path_stack. push ( node. name ( ) . as_os_str ( ) ) ;
314321 if node. children ( ) . is_empty ( ) {
315- let color = file_color ( & fs_path_stack . iter ( ) . collect :: < PathBuf > ( ) ) ;
316- map. insert ( key_stack . clone ( ) , color) ;
322+ let color = file_color ( & path_stack . iter ( ) . collect :: < PathBuf > ( ) ) ;
323+ map. insert ( path_stack . iter ( ) . map ( |s| s . to_os_string ( ) ) . collect ( ) , color) ;
317324 } else {
318325 for child in node. children ( ) {
319- build_coloring_map ( child, key_stack , fs_path_stack , map) ;
326+ build_coloring_map ( child, path_stack , map) ;
320327 }
321328 }
322- key_stack. pop ( ) ;
323- fs_path_stack. pop ( ) ;
329+ path_stack. pop ( ) ;
324330}
325331
326332fn file_color ( path : & Path ) -> Color {
0 commit comments