Skip to content

Commit 6477254

Browse files
committed
Final touches :D
1 parent 5f4875e commit 6477254

File tree

2 files changed

+63
-35
lines changed

2 files changed

+63
-35
lines changed

src/app_state.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ impl AppState {
271271
Ok(exercise.path)
272272
}
273273

274+
// Reset the exercise by index and return its name.
274275
pub fn reset_exercise_by_ind(&mut self, exercise_ind: usize) -> Result<&'static str> {
275276
if exercise_ind >= self.exercises.len() {
276277
bail!(BAD_INDEX_ERR);
@@ -280,7 +281,7 @@ impl AppState {
280281
let exercise = &self.exercises[exercise_ind];
281282
self.reset(exercise_ind, exercise.path)?;
282283

283-
Ok(exercise.path)
284+
Ok(exercise.name)
284285
}
285286

286287
// Return the index of the next pending exercise or `None` if all exercises are done.

src/list/state.rs

Lines changed: 61 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use std::{
1212

1313
use crate::{app_state::AppState, exercise::Exercise, term::progress_bar, MAX_EXERCISE_NAME_LEN};
1414

15-
// +1 for padding.
15+
const MAX_SCROLL_PADDING: usize = 8;
16+
// +1 for column padding.
1617
const SPACE: &[u8] = &[b' '; MAX_EXERCISE_NAME_LEN + 1];
1718

1819
fn next_ln(stdout: &mut StdoutLock) -> io::Result<()> {
@@ -44,16 +45,16 @@ pub struct ListState<'a> {
4445
name_col_width: usize,
4546
filter: Filter,
4647
n_rows_with_filter: usize,
47-
/// Selected row out of the displayed ones.
48+
/// Selected row out of the filtered ones.
4849
selected_row: Option<usize>,
50+
row_offset: usize,
4951
term_width: u16,
5052
term_height: u16,
5153
separator_line: Vec<u8>,
5254
narrow_term: bool,
5355
show_footer: bool,
5456
max_n_rows_to_display: usize,
5557
scroll_padding: usize,
56-
row_offset: usize,
5758
}
5859

5960
impl<'a> ListState<'a> {
@@ -70,15 +71,16 @@ impl<'a> ListState<'a> {
7071

7172
let filter = Filter::None;
7273
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();
7475

7576
let mut slf = Self {
7677
message: String::with_capacity(128),
7778
app_state,
7879
name_col_width,
7980
filter,
8081
n_rows_with_filter,
81-
selected_row: selected,
82+
selected_row: Some(selected),
83+
row_offset: selected.saturating_sub(MAX_SCROLL_PADDING),
8284
// Set by `set_term_size`
8385
term_width: 0,
8486
term_height: 0,
@@ -87,8 +89,6 @@ impl<'a> ListState<'a> {
8789
show_footer: true,
8890
max_n_rows_to_display: 0,
8991
scroll_padding: 0,
90-
// Updated by `draw`
91-
row_offset: 0,
9292
};
9393

9494
let (width, height) = terminal::size()?;
@@ -98,19 +98,6 @@ impl<'a> ListState<'a> {
9898
Ok(slf)
9999
}
100100

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-
114101
fn update_offset(&mut self) {
115102
let Some(selected) = self.selected_row else {
116103
return;
@@ -130,6 +117,36 @@ impl<'a> ListState<'a> {
130117
.min(global_max_offset);
131118
}
132119

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+
133150
fn draw_rows(
134151
&self,
135152
stdout: &mut StdoutLock,
@@ -196,8 +213,6 @@ impl<'a> ListState<'a> {
196213
stdout.write_all(b"Path")?;
197214
next_ln_overwrite(stdout)?;
198215

199-
self.update_offset();
200-
201216
// Rows
202217
let iter = self.app_state.exercises().iter().enumerate();
203218
let n_displayed_rows = match self.filter {
@@ -228,7 +243,7 @@ impl<'a> ListState<'a> {
228243
next_ln(stdout)?;
229244

230245
if self.message.is_empty() {
231-
// Help footer
246+
// Help footer message
232247
if self.selected_row.is_some() {
233248
stdout.write_all(
234249
"↓/j ↑/k home/g end/G | <c>ontinue at | <r>eset exercise".as_bytes(),
@@ -240,6 +255,7 @@ impl<'a> ListState<'a> {
240255
stdout.write_all(b" | filter ")?;
241256
}
242257
} else {
258+
// Nothing selected (and nothing shown), so only display filter and quit.
243259
stdout.write_all(b"filter ")?;
244260
}
245261

@@ -262,7 +278,9 @@ impl<'a> ListState<'a> {
262278
}
263279
Filter::None => stdout.write_all(b"<d>one/<p>ending")?,
264280
}
281+
265282
stdout.write_all(b" | <q>uit list")?;
283+
266284
if self.narrow_term {
267285
next_ln_overwrite(stdout)?;
268286
} else {
@@ -282,6 +300,11 @@ impl<'a> ListState<'a> {
282300
stdout.queue(EndSynchronizedUpdate)?.flush()
283301
}
284302

303+
fn set_selected(&mut self, selected: usize) {
304+
self.selected_row = Some(selected);
305+
self.update_offset();
306+
}
307+
285308
fn update_rows(&mut self) {
286309
self.n_rows_with_filter = match self.filter {
287310
Filter::Done => self
@@ -301,12 +324,13 @@ impl<'a> ListState<'a> {
301324

302325
if self.n_rows_with_filter == 0 {
303326
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;
309328
}
329+
330+
self.set_selected(
331+
self.selected_row
332+
.map_or(0, |selected| selected.min(self.n_rows_with_filter - 1)),
333+
);
310334
}
311335

312336
#[inline]
@@ -321,25 +345,25 @@ impl<'a> ListState<'a> {
321345

322346
pub fn select_next(&mut self) {
323347
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));
325349
}
326350
}
327351

328352
pub fn select_previous(&mut self) {
329353
if let Some(selected) = self.selected_row {
330-
self.selected_row = Some(selected.saturating_sub(1));
354+
self.set_selected(selected.saturating_sub(1));
331355
}
332356
}
333357

334358
pub fn select_first(&mut self) {
335359
if self.n_rows_with_filter > 0 {
336-
self.selected_row = Some(0);
360+
self.set_selected(0);
337361
}
338362
}
339363

340364
pub fn select_last(&mut self) {
341365
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);
343367
}
344368
}
345369

@@ -374,9 +398,12 @@ impl<'a> ListState<'a> {
374398
};
375399

376400
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)?;
378402
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+
)?;
380407

381408
Ok(())
382409
}

0 commit comments

Comments
 (0)