@@ -12,7 +12,8 @@ use std::{
12
12
13
13
use crate :: { app_state:: AppState , exercise:: Exercise , term:: progress_bar, MAX_EXERCISE_NAME_LEN } ;
14
14
15
- // +1 for padding.
15
+ const MAX_SCROLL_PADDING : usize = 8 ;
16
+ // +1 for column padding.
16
17
const SPACE : & [ u8 ] = & [ b' ' ; MAX_EXERCISE_NAME_LEN + 1 ] ;
17
18
18
19
fn next_ln ( stdout : & mut StdoutLock ) -> io:: Result < ( ) > {
@@ -44,16 +45,16 @@ pub struct ListState<'a> {
44
45
name_col_width : usize ,
45
46
filter : Filter ,
46
47
n_rows_with_filter : usize ,
47
- /// Selected row out of the displayed ones.
48
+ /// Selected row out of the filtered ones.
48
49
selected_row : Option < usize > ,
50
+ row_offset : usize ,
49
51
term_width : u16 ,
50
52
term_height : u16 ,
51
53
separator_line : Vec < u8 > ,
52
54
narrow_term : bool ,
53
55
show_footer : bool ,
54
56
max_n_rows_to_display : usize ,
55
57
scroll_padding : usize ,
56
- row_offset : usize ,
57
58
}
58
59
59
60
impl < ' a > ListState < ' a > {
@@ -70,15 +71,16 @@ impl<'a> ListState<'a> {
70
71
71
72
let filter = Filter :: None ;
72
73
let n_rows_with_filter = app_state. exercises ( ) . len ( ) ;
73
- let selected = Some ( app_state. current_exercise_ind ( ) ) ;
74
+ let selected = app_state. current_exercise_ind ( ) ;
74
75
75
76
let mut slf = Self {
76
77
message : String :: with_capacity ( 128 ) ,
77
78
app_state,
78
79
name_col_width,
79
80
filter,
80
81
n_rows_with_filter,
81
- selected_row : selected,
82
+ selected_row : Some ( selected) ,
83
+ row_offset : selected. saturating_sub ( MAX_SCROLL_PADDING ) ,
82
84
// Set by `set_term_size`
83
85
term_width : 0 ,
84
86
term_height : 0 ,
@@ -87,8 +89,6 @@ impl<'a> ListState<'a> {
87
89
show_footer : true ,
88
90
max_n_rows_to_display : 0 ,
89
91
scroll_padding : 0 ,
90
- // Updated by `draw`
91
- row_offset : 0 ,
92
92
} ;
93
93
94
94
let ( width, height) = terminal:: size ( ) ?;
@@ -98,19 +98,6 @@ impl<'a> ListState<'a> {
98
98
Ok ( slf)
99
99
}
100
100
101
- pub fn set_term_size ( & mut self , width : u16 , height : u16 ) {
102
- self . term_width = width;
103
- self . term_height = height;
104
-
105
- self . separator_line = "─" . as_bytes ( ) . repeat ( width as usize ) ;
106
-
107
- self . narrow_term = width < 95 && self . selected_row . is_some ( ) ;
108
- self . show_footer = height > 6 ;
109
- self . max_n_rows_to_display =
110
- ( height - 1 - u16:: from ( self . show_footer ) * ( 4 + u16:: from ( self . narrow_term ) ) ) as usize ;
111
- self . scroll_padding = ( self . max_n_rows_to_display / 4 ) . min ( 5 ) ;
112
- }
113
-
114
101
fn update_offset ( & mut self ) {
115
102
let Some ( selected) = self . selected_row else {
116
103
return ;
@@ -130,6 +117,36 @@ impl<'a> ListState<'a> {
130
117
. min ( global_max_offset) ;
131
118
}
132
119
120
+ pub fn set_term_size ( & mut self , width : u16 , height : u16 ) {
121
+ self . term_width = width;
122
+ self . term_height = height;
123
+
124
+ if height == 0 {
125
+ return ;
126
+ }
127
+
128
+ let wide_help_footer_width = 95 ;
129
+ // The help footer is shorter when nothing is selected.
130
+ self . narrow_term = width < wide_help_footer_width && self . selected_row . is_some ( ) ;
131
+
132
+ let header_height = 1 ;
133
+ // 2 separator, 1 progress bar, 1-2 footer message.
134
+ let footer_height = 4 + u16:: from ( self . narrow_term ) ;
135
+ self . show_footer = height > header_height + footer_height;
136
+
137
+ if self . show_footer {
138
+ self . separator_line = "─" . as_bytes ( ) . repeat ( width as usize ) ;
139
+ }
140
+
141
+ self . max_n_rows_to_display = height
142
+ . saturating_sub ( header_height + u16:: from ( self . show_footer ) * footer_height)
143
+ as usize ;
144
+
145
+ self . scroll_padding = ( self . max_n_rows_to_display / 4 ) . min ( MAX_SCROLL_PADDING ) ;
146
+
147
+ self . update_offset ( ) ;
148
+ }
149
+
133
150
fn draw_rows (
134
151
& self ,
135
152
stdout : & mut StdoutLock ,
@@ -196,8 +213,6 @@ impl<'a> ListState<'a> {
196
213
stdout. write_all ( b"Path" ) ?;
197
214
next_ln_overwrite ( stdout) ?;
198
215
199
- self . update_offset ( ) ;
200
-
201
216
// Rows
202
217
let iter = self . app_state . exercises ( ) . iter ( ) . enumerate ( ) ;
203
218
let n_displayed_rows = match self . filter {
@@ -228,7 +243,7 @@ impl<'a> ListState<'a> {
228
243
next_ln ( stdout) ?;
229
244
230
245
if self . message . is_empty ( ) {
231
- // Help footer
246
+ // Help footer message
232
247
if self . selected_row . is_some ( ) {
233
248
stdout. write_all (
234
249
"↓/j ↑/k home/g end/G | <c>ontinue at | <r>eset exercise" . as_bytes ( ) ,
@@ -240,6 +255,7 @@ impl<'a> ListState<'a> {
240
255
stdout. write_all ( b" | filter " ) ?;
241
256
}
242
257
} else {
258
+ // Nothing selected (and nothing shown), so only display filter and quit.
243
259
stdout. write_all ( b"filter " ) ?;
244
260
}
245
261
@@ -262,7 +278,9 @@ impl<'a> ListState<'a> {
262
278
}
263
279
Filter :: None => stdout. write_all ( b"<d>one/<p>ending" ) ?,
264
280
}
281
+
265
282
stdout. write_all ( b" | <q>uit list" ) ?;
283
+
266
284
if self . narrow_term {
267
285
next_ln_overwrite ( stdout) ?;
268
286
} else {
@@ -282,6 +300,11 @@ impl<'a> ListState<'a> {
282
300
stdout. queue ( EndSynchronizedUpdate ) ?. flush ( )
283
301
}
284
302
303
+ fn set_selected ( & mut self , selected : usize ) {
304
+ self . selected_row = Some ( selected) ;
305
+ self . update_offset ( ) ;
306
+ }
307
+
285
308
fn update_rows ( & mut self ) {
286
309
self . n_rows_with_filter = match self . filter {
287
310
Filter :: Done => self
@@ -301,12 +324,13 @@ impl<'a> ListState<'a> {
301
324
302
325
if self . n_rows_with_filter == 0 {
303
326
self . selected_row = None ;
304
- } else {
305
- self . selected_row = Some (
306
- self . selected_row
307
- . map_or ( 0 , |selected| selected. min ( self . n_rows_with_filter - 1 ) ) ,
308
- ) ;
327
+ return ;
309
328
}
329
+
330
+ self . set_selected (
331
+ self . selected_row
332
+ . map_or ( 0 , |selected| selected. min ( self . n_rows_with_filter - 1 ) ) ,
333
+ ) ;
310
334
}
311
335
312
336
#[ inline]
@@ -321,25 +345,25 @@ impl<'a> ListState<'a> {
321
345
322
346
pub fn select_next ( & mut self ) {
323
347
if let Some ( selected) = self . selected_row {
324
- self . selected_row = Some ( ( selected + 1 ) . min ( self . n_rows_with_filter - 1 ) ) ;
348
+ self . set_selected ( ( selected + 1 ) . min ( self . n_rows_with_filter - 1 ) ) ;
325
349
}
326
350
}
327
351
328
352
pub fn select_previous ( & mut self ) {
329
353
if let Some ( selected) = self . selected_row {
330
- self . selected_row = Some ( selected. saturating_sub ( 1 ) ) ;
354
+ self . set_selected ( selected. saturating_sub ( 1 ) ) ;
331
355
}
332
356
}
333
357
334
358
pub fn select_first ( & mut self ) {
335
359
if self . n_rows_with_filter > 0 {
336
- self . selected_row = Some ( 0 ) ;
360
+ self . set_selected ( 0 ) ;
337
361
}
338
362
}
339
363
340
364
pub fn select_last ( & mut self ) {
341
365
if self . n_rows_with_filter > 0 {
342
- self . selected_row = Some ( self . n_rows_with_filter - 1 ) ;
366
+ self . set_selected ( self . n_rows_with_filter - 1 ) ;
343
367
}
344
368
}
345
369
@@ -374,9 +398,12 @@ impl<'a> ListState<'a> {
374
398
} ;
375
399
376
400
let exercise_ind = self . selected_to_exercise_ind ( selected) ?;
377
- let exercise_path = self . app_state . reset_exercise_by_ind ( exercise_ind) ?;
401
+ let exercise_name = self . app_state . reset_exercise_by_ind ( exercise_ind) ?;
378
402
self . update_rows ( ) ;
379
- write ! ( self . message, "The exercise {exercise_path} has been reset" ) ?;
403
+ write ! (
404
+ self . message,
405
+ "The exercise `{exercise_name}` has been reset" ,
406
+ ) ?;
380
407
381
408
Ok ( ( ) )
382
409
}
0 commit comments