@@ -4,7 +4,7 @@ use crate::interactive::{
44 DisplayOptions , EntryDataBundle , SortMode ,
55} ;
66use chrono:: DateTime ;
7- use dua:: traverse:: { Tree , TreeIndex } ;
7+ use dua:: traverse:: { EntryData , Tree , TreeIndex } ;
88use itertools:: Itertools ;
99use std:: { borrow:: Borrow , path:: Path } ;
1010use tui:: {
@@ -65,36 +65,12 @@ impl Entries {
6565 } ;
6666
6767 let total: u128 = entries. iter ( ) . map ( |b| b. data . size ) . sum ( ) ;
68- let title = match path_of ( tree, * root) . to_string_lossy ( ) . to_string ( ) {
69- ref p if p. is_empty ( ) => Path :: new ( "." )
70- . canonicalize ( )
71- . map ( |p| p. to_string_lossy ( ) . to_string ( ) )
72- . unwrap_or_else ( |_| String :: from ( "." ) ) ,
73- p => p,
74- } ;
75- let title = format ! (
76- " {} ({} item{}) " ,
77- title,
78- entries. len( ) ,
79- match entries. len( ) {
80- 1 => "" ,
81- _ => "s" ,
82- }
83- ) ;
84- let block = Block :: default ( )
85- . title ( title. as_str ( ) )
86- . border_style ( * border_style)
87- . borders ( Borders :: ALL ) ;
88- let entry_in_view = selected. map ( |selected| {
89- entries
90- . iter ( )
91- . find_position ( |b| b. index == selected)
92- . map ( |( idx, _) | idx)
93- . unwrap_or ( 0 )
94- } ) ;
68+ let title = title ( & current_path ( tree, root) , entries. len ( ) ) ;
69+ let title_block = title_block ( & title, border_style) ;
70+ let entry_in_view = entry_in_view ( selected, entries) ;
9571
9672 let props = ListProps {
97- block : Some ( block ) ,
73+ block : Some ( title_block ) ,
9874 entry_in_view,
9975 } ;
10076 let lines = entries. iter ( ) . map (
@@ -104,129 +80,222 @@ impl Entries {
10480 is_dir,
10581 exists,
10682 } | {
107- let mut style = Style :: default ( ) ;
108- let is_selected = if let Some ( idx) = selected {
109- * idx == * node_idx
110- } else {
111- false
112- } ;
113- if is_selected {
114- style. add_modifier . insert ( Modifier :: REVERSED ) ;
115- }
116- if * is_focussed & is_selected {
117- style. add_modifier . insert ( Modifier :: BOLD ) ;
118- }
119-
83+ let is_selected = is_selected ( selected, node_idx) ;
12084 let fraction = w. size as f32 / total as f32 ;
121- let should_avoid_showing_a_big_reversed_bar = fraction > 0.9 ;
122- let local_style = if should_avoid_showing_a_big_reversed_bar {
123- style. remove_modifier ( Modifier :: REVERSED )
124- } else {
125- style
126- } ;
127-
128- let datetime = DateTime :: < chrono:: Utc > :: from ( w. mtime ) ;
129- let formatted_time = datetime. format ( "%d/%m/%Y %H:%M:%S" ) . to_string ( ) ;
130- let mtime = Span :: styled (
131- format ! ( "{:>20}" , formatted_time) ,
132- Style {
133- fg : match sort_mode {
134- SortMode :: SizeAscending | SortMode :: SizeDescending => style. fg ,
135- SortMode :: MTimeAscending | SortMode :: MTimeDescending => {
136- Color :: Green . into ( )
137- }
138- } ,
139- ..style
140- } ,
141- ) ;
142-
143- let bar = Span :: styled ( " | " , local_style) ;
144-
145- let bytes = Span :: styled (
146- format ! (
147- "{:>byte_column_width$}" ,
148- display. byte_format. display( w. size) . to_string( ) , // we would have to impl alignment/padding ourselves otherwise...
149- byte_column_width = display. byte_format. width( )
150- ) ,
151- Style {
152- fg : match sort_mode {
153- SortMode :: SizeAscending | SortMode :: SizeDescending => {
154- Color :: Green . into ( )
155- }
156- SortMode :: MTimeAscending | SortMode :: MTimeDescending => style. fg ,
157- } ,
158- ..style
159- } ,
160- ) ;
161-
162- let left_bar = Span :: styled ( " |" , local_style) ;
163- let percentage = Span :: styled (
164- format ! ( "{}" , display. byte_vis. display( fraction) ) ,
165- local_style,
166- ) ;
167- let right_bar = Span :: styled ( "| " , local_style) ;
168-
169- let name = Span :: styled (
170- fill_background_to_right (
171- format ! (
172- "{prefix}{}" ,
173- w. name. to_string_lossy( ) ,
174- prefix = if * is_dir && !is_top( * root) { "/" } else { " " }
175- ) ,
176- area. width ,
177- ) ,
178- {
179- let is_marked = marked. map ( |m| m. contains_key ( node_idx) ) . unwrap_or ( false ) ;
180- let fg = if !exists {
181- // non-existing - always red!
182- Some ( Color :: Red )
183- } else {
184- entry_color ( style. fg , !* is_dir, is_marked)
185- } ;
186- Style { fg, ..style }
187- } ,
188- ) ;
85+ let style = style ( is_selected, is_focussed) ;
18986
87+ let mut columns = Vec :: new ( ) ;
19088 if should_show_mtime_column ( sort_mode) {
191- vec ! [ mtime, bar, bytes, left_bar, percentage, right_bar, name]
192- } else {
193- vec ! [ bytes, left_bar, percentage, right_bar, name]
89+ columns. push ( mtime_column ( w, sort_mode, style) ) ;
19490 }
91+ columns. push ( bytes_column ( display, w, sort_mode, style) ) ;
92+ columns. push ( percentage_column ( display, fraction, style) ) ;
93+ columns. push ( name_column (
94+ w, is_dir, is_top, root, area, marked, node_idx, exists, style,
95+ ) ) ;
96+
97+ columns_with_separators ( columns, style)
19598 } ,
19699 ) ;
197100
198101 list. render ( props, lines, area, buf) ;
199102
200103 if * is_focussed {
201- let help_text = " . = o|.. = u ── ⇊ = Ctrl+d|↓ = j|⇈ = Ctrl+u|↑ = k " ;
202- let help_text_block_width = block_width ( help_text) ;
203- let bound = Rect {
204- width : area. width . saturating_sub ( 1 ) ,
205- ..area
206- } ;
207- if block_width ( & title) + help_text_block_width <= bound. width {
208- draw_text_nowrap_fn (
209- rect:: snap_to_right ( bound, help_text_block_width) ,
210- buf,
211- help_text,
212- |_, _, _| Style :: default ( ) ,
213- ) ;
214- }
215- let bound = line_bound ( bound, bound. height . saturating_sub ( 1 ) as usize ) ;
216- let help_text = " mark-move = d | mark-toggle = space | toggle-all = a" ;
217- let help_text_block_width = block_width ( help_text) ;
218- if help_text_block_width <= bound. width {
219- draw_text_nowrap_fn (
220- rect:: snap_to_right ( bound, help_text_block_width) ,
221- buf,
222- help_text,
223- |_, _, _| Style :: default ( ) ,
224- ) ;
225- }
104+ let bound = draw_top_right_help ( area, title, buf) ;
105+ draw_bottom_right_help ( bound, buf) ;
226106 }
227107 }
228108}
229109
110+ fn entry_in_view (
111+ selected : & Option < petgraph:: stable_graph:: NodeIndex > ,
112+ entries : & & [ EntryDataBundle ] ,
113+ ) -> Option < usize > {
114+ selected. map ( |selected| {
115+ entries
116+ . iter ( )
117+ . find_position ( |b| b. index == selected)
118+ . map ( |( idx, _) | idx)
119+ . unwrap_or ( 0 )
120+ } )
121+ }
122+
123+ fn title_block < ' a > ( title : & ' a str , border_style : & ' a Style ) -> Block < ' a > {
124+ Block :: default ( )
125+ . title ( title)
126+ . border_style ( * border_style)
127+ . borders ( Borders :: ALL )
128+ }
129+
130+ fn title ( current_path : & str , item_count : usize ) -> String {
131+ let title = format ! (
132+ " {} ({} item{}) " ,
133+ current_path,
134+ item_count,
135+ match item_count {
136+ 1 => "" ,
137+ _ => "s" ,
138+ }
139+ ) ;
140+ title
141+ }
142+
143+ fn current_path (
144+ tree : & petgraph:: stable_graph:: StableGraph < EntryData , ( ) > ,
145+ root : & petgraph:: stable_graph:: NodeIndex ,
146+ ) -> String {
147+ match path_of ( tree, * root) . to_string_lossy ( ) . to_string ( ) {
148+ ref p if p. is_empty ( ) => Path :: new ( "." )
149+ . canonicalize ( )
150+ . map ( |p| p. to_string_lossy ( ) . to_string ( ) )
151+ . unwrap_or_else ( |_| String :: from ( "." ) ) ,
152+ p => p,
153+ }
154+ }
155+
156+ fn draw_bottom_right_help ( bound : Rect , buf : & mut Buffer ) {
157+ let bound = line_bound ( bound, bound. height . saturating_sub ( 1 ) as usize ) ;
158+ let help_text = " mark-move = d | mark-toggle = space | toggle-all = a" ;
159+ let help_text_block_width = block_width ( help_text) ;
160+ if help_text_block_width <= bound. width {
161+ draw_text_nowrap_fn (
162+ rect:: snap_to_right ( bound, help_text_block_width) ,
163+ buf,
164+ help_text,
165+ |_, _, _| Style :: default ( ) ,
166+ ) ;
167+ }
168+ }
169+
170+ fn draw_top_right_help ( area : Rect , title : String , buf : & mut Buffer ) -> Rect {
171+ let help_text = " . = o|.. = u ── ⇊ = Ctrl+d|↓ = j|⇈ = Ctrl+u|↑ = k " ;
172+ let help_text_block_width = block_width ( help_text) ;
173+ let bound = Rect {
174+ width : area. width . saturating_sub ( 1 ) ,
175+ ..area
176+ } ;
177+ if block_width ( & title) + help_text_block_width <= bound. width {
178+ draw_text_nowrap_fn (
179+ rect:: snap_to_right ( bound, help_text_block_width) ,
180+ buf,
181+ help_text,
182+ |_, _, _| Style :: default ( ) ,
183+ ) ;
184+ }
185+ bound
186+ }
187+
188+ fn is_selected (
189+ selected : & Option < petgraph:: stable_graph:: NodeIndex > ,
190+ node_idx : & petgraph:: stable_graph:: NodeIndex ,
191+ ) -> bool {
192+ let is_selected = if let Some ( idx) = selected {
193+ * idx == * node_idx
194+ } else {
195+ false
196+ } ;
197+ is_selected
198+ }
199+
200+ fn style ( is_selected : bool , is_focussed : & bool ) -> Style {
201+ let mut style = Style :: default ( ) ;
202+ if is_selected {
203+ style. add_modifier . insert ( Modifier :: REVERSED ) ;
204+ }
205+ if * is_focussed & is_selected {
206+ style. add_modifier . insert ( Modifier :: BOLD ) ;
207+ }
208+ style
209+ }
210+
211+ fn columns_with_separators ( columns : Vec < Span < ' _ > > , style : Style ) -> Vec < Span < ' _ > > {
212+ let mut columns_with_separators = Vec :: new ( ) ;
213+ let column_count = columns. len ( ) ;
214+ for ( idx, column) in columns. into_iter ( ) . enumerate ( ) {
215+ columns_with_separators. push ( column) ;
216+ if idx != column_count - 1 {
217+ columns_with_separators. push ( Span :: styled ( " | " , style) )
218+ }
219+ }
220+ columns_with_separators
221+ }
222+
223+ fn mtime_column < ' a > ( w : & ' a EntryData , sort_mode : & ' a SortMode , style : Style ) -> Span < ' a > {
224+ let datetime = DateTime :: < chrono:: Utc > :: from ( w. mtime ) ;
225+ let formatted_time = datetime. format ( "%d/%m/%Y %H:%M:%S" ) . to_string ( ) ;
226+ Span :: styled (
227+ format ! ( "{:>20}" , formatted_time) ,
228+ Style {
229+ fg : match sort_mode {
230+ SortMode :: SizeAscending | SortMode :: SizeDescending => style. fg ,
231+ SortMode :: MTimeAscending | SortMode :: MTimeDescending => Color :: Green . into ( ) ,
232+ } ,
233+ ..style
234+ } ,
235+ )
236+ }
237+
238+ fn name_column < ' a > (
239+ w : & ' a dua:: traverse:: EntryData ,
240+ is_dir : & ' a bool ,
241+ is_top : impl Fn ( petgraph:: stable_graph:: NodeIndex ) -> bool ,
242+ root : & ' a petgraph:: stable_graph:: NodeIndex ,
243+ area : Rect ,
244+ marked : & Option <
245+ & std:: collections:: BTreeMap < petgraph:: stable_graph:: NodeIndex , super :: EntryMark > ,
246+ > ,
247+ node_idx : & ' a petgraph:: stable_graph:: NodeIndex ,
248+ exists : & ' a bool ,
249+ style : Style ,
250+ ) -> Span < ' a > {
251+ Span :: styled (
252+ fill_background_to_right (
253+ format ! (
254+ "{prefix}{}" ,
255+ w. name. to_string_lossy( ) ,
256+ prefix = if * is_dir && !is_top( * root) { "/" } else { " " }
257+ ) ,
258+ area. width ,
259+ ) ,
260+ {
261+ let is_marked = marked. map ( |m| m. contains_key ( & node_idx) ) . unwrap_or ( false ) ;
262+ let fg = if !exists {
263+ // non-existing - always red!
264+ Some ( Color :: Red )
265+ } else {
266+ entry_color ( style. fg , !is_dir, is_marked)
267+ } ;
268+ Style { fg, ..style }
269+ } ,
270+ )
271+ }
272+
273+ fn percentage_column < ' a > ( display : & ' a DisplayOptions , fraction : f32 , style : Style ) -> Span < ' a > {
274+ Span :: styled ( format ! ( "{}" , display. byte_vis. display( fraction) ) , style)
275+ }
276+
277+ fn bytes_column < ' a > (
278+ display : & ' a DisplayOptions ,
279+ w : & ' a dua:: traverse:: EntryData ,
280+ sort_mode : & ' a SortMode ,
281+ style : Style ,
282+ ) -> Span < ' a > {
283+ Span :: styled (
284+ format ! (
285+ "{:>byte_column_width$}" ,
286+ display. byte_format. display( w. size) . to_string( ) , // we would have to impl alignment/padding ourselves otherwise...
287+ byte_column_width = display. byte_format. width( )
288+ ) ,
289+ Style {
290+ fg : match sort_mode {
291+ SortMode :: SizeAscending | SortMode :: SizeDescending => Color :: Green . into ( ) ,
292+ SortMode :: MTimeAscending | SortMode :: MTimeDescending => style. fg ,
293+ } ,
294+ ..style
295+ } ,
296+ )
297+ }
298+
230299fn should_show_mtime_column ( sort_mode : & SortMode ) -> bool {
231300 matches ! (
232301 sort_mode,
0 commit comments