mirror of
https://github.com/rust-lang/rustlings.git
synced 2024-12-26 00:00:03 +03:00
Compare commits
8 commits
8d7f82296f
...
45099dc919
Author | SHA1 | Date | |
---|---|---|---|
45099dc919 | |||
dd52e9cd72 | |||
0f71a150ff | |||
59e8f70e55 | |||
4c8365fe88 | |||
52af0674c1 | |||
938b90e5f2 | |||
55cc8584bd |
|
@ -116,6 +116,8 @@ bin = [
|
||||||
{ name = "generics1_sol", path = "../solutions/14_generics/generics1.rs" },
|
{ name = "generics1_sol", path = "../solutions/14_generics/generics1.rs" },
|
||||||
{ name = "generics2", path = "../exercises/14_generics/generics2.rs" },
|
{ name = "generics2", path = "../exercises/14_generics/generics2.rs" },
|
||||||
{ name = "generics2_sol", path = "../solutions/14_generics/generics2.rs" },
|
{ name = "generics2_sol", path = "../solutions/14_generics/generics2.rs" },
|
||||||
|
{ name = "generics3", path = "../exercises/14_generics/generics3.rs" },
|
||||||
|
{ name = "generics3_sol", path = "../solutions/14_generics/generics3.rs" },
|
||||||
{ name = "traits1", path = "../exercises/15_traits/traits1.rs" },
|
{ name = "traits1", path = "../exercises/15_traits/traits1.rs" },
|
||||||
{ name = "traits1_sol", path = "../solutions/15_traits/traits1.rs" },
|
{ name = "traits1_sol", path = "../solutions/15_traits/traits1.rs" },
|
||||||
{ name = "traits2", path = "../exercises/15_traits/traits2.rs" },
|
{ name = "traits2", path = "../exercises/15_traits/traits2.rs" },
|
||||||
|
|
54
exercises/14_generics/generics3.rs
Normal file
54
exercises/14_generics/generics3.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// generics3.rs
|
||||||
|
// Execute `rustlings hint generics3` or use the `hint` watch subcommand for a hint.
|
||||||
|
|
||||||
|
// This function should take an array of `Option` elements and returns array of not None elements
|
||||||
|
// TODO fix this function signature
|
||||||
|
fn into_dispose_nulls(list: Vec<Option<&str>>) -> Vec<&str> {
|
||||||
|
list.into_iter().flatten().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// You can optionally experiment here.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn store_str_on_list() {
|
||||||
|
let names_list = vec![Some("maria"), Some("jacob"), None, Some("kacper"), None];
|
||||||
|
let only_values = into_dispose_nulls(names_list);
|
||||||
|
assert_eq!(only_values.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn store_numbers_on_list() {
|
||||||
|
let numbers_list = vec![Some(1), Some(2), None, Some(3)];
|
||||||
|
let only_values = into_dispose_nulls(numbers_list);
|
||||||
|
assert_eq!(only_values.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn store_custom_type_on_list() {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct Rectangle {
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
}
|
||||||
|
impl Rectangle {
|
||||||
|
fn new(width: i32, height: i32) -> Self {
|
||||||
|
Self { width, height }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let custom_list = vec![
|
||||||
|
Some(Rectangle::new(1, 2)),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(Rectangle::new(3, 4)),
|
||||||
|
];
|
||||||
|
let only_values = into_dispose_nulls(custom_list);
|
||||||
|
assert_eq!(only_values.len(), 2);
|
||||||
|
}
|
||||||
|
}
|
|
@ -744,6 +744,17 @@ hint = """
|
||||||
Related section in The Book:
|
Related section in The Book:
|
||||||
https://doc.rust-lang.org/book/ch10-01-syntax.html#in-method-definitions"""
|
https://doc.rust-lang.org/book/ch10-01-syntax.html#in-method-definitions"""
|
||||||
|
|
||||||
|
[[exercises]]
|
||||||
|
name = "generics3"
|
||||||
|
dir = "14_generics"
|
||||||
|
hint = """
|
||||||
|
Vectors in Rust use generics to create dynamically-sized arrays of any type.
|
||||||
|
The `into_dispose_nulls` function takes a vector as an argument, but only accepts vectors that store the &str type.
|
||||||
|
To allow the function to accept vectors that store any type, you can leverage your knowledge about generics.
|
||||||
|
If you're unsure how to proceed, please refer to the Rust Book at:
|
||||||
|
https://doc.rust-lang.org/book/ch10-01-syntax.html#in-function-definitions.
|
||||||
|
"""
|
||||||
|
|
||||||
# TRAITS
|
# TRAITS
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
|
|
53
solutions/14_generics/generics3.rs
Normal file
53
solutions/14_generics/generics3.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// generics3.rs
|
||||||
|
// Execute `rustlings hint generics3` or use the `hint` watch subcommand for a hint.
|
||||||
|
|
||||||
|
// Here we added generic type `T` to function signature
|
||||||
|
// Now this function can be used with vector of any
|
||||||
|
fn into_dispose_nulls<T>(list: Vec<Option<T>>) -> Vec<T> {
|
||||||
|
list.into_iter().flatten().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// You can optionally experiment here.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn store_str_on_list() {
|
||||||
|
let names_list = vec![Some("maria"), Some("jacob"), None, Some("kacper"), None];
|
||||||
|
let only_values = into_dispose_nulls(names_list);
|
||||||
|
assert_eq!(only_values.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn store_numbers_on_list() {
|
||||||
|
let numbers_list = vec![Some(1), Some(2), None, Some(3)];
|
||||||
|
let only_values = into_dispose_nulls(numbers_list);
|
||||||
|
assert_eq!(only_values.len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn store_custom_type_on_list() {
|
||||||
|
struct Rectangle {
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
}
|
||||||
|
impl Rectangle {
|
||||||
|
fn new(width: i32, height: i32) -> Self {
|
||||||
|
Self { width, height }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let custom_list = vec![
|
||||||
|
Some(Rectangle::new(1, 2)),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Some(Rectangle::new(3, 4)),
|
||||||
|
];
|
||||||
|
let only_values = into_dispose_nulls(custom_list);
|
||||||
|
assert_eq!(only_values.len(), 2);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ use crate::app_state::AppState;
|
||||||
|
|
||||||
use self::state::{Filter, ListState};
|
use self::state::{Filter, ListState};
|
||||||
|
|
||||||
|
mod scroll_state;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
fn handle_list(app_state: &mut AppState, stdout: &mut StdoutLock) -> Result<()> {
|
fn handle_list(app_state: &mut AppState, stdout: &mut StdoutLock) -> Result<()> {
|
||||||
|
|
104
src/list/scroll_state.rs
Normal file
104
src/list/scroll_state.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
pub struct ScrollState {
|
||||||
|
n_rows: usize,
|
||||||
|
max_n_rows_to_display: usize,
|
||||||
|
selected: Option<usize>,
|
||||||
|
offset: usize,
|
||||||
|
scroll_padding: usize,
|
||||||
|
max_scroll_padding: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScrollState {
|
||||||
|
pub fn new(n_rows: usize, selected: Option<usize>, max_scroll_padding: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
n_rows,
|
||||||
|
max_n_rows_to_display: 0,
|
||||||
|
selected,
|
||||||
|
offset: selected.map_or(0, |selected| selected.saturating_sub(max_scroll_padding)),
|
||||||
|
scroll_padding: 0,
|
||||||
|
max_scroll_padding,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn offset(&self) -> usize {
|
||||||
|
self.offset
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_offset(&mut self) {
|
||||||
|
let Some(selected) = self.selected else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let min_offset = (selected + self.scroll_padding)
|
||||||
|
.saturating_sub(self.max_n_rows_to_display.saturating_sub(1));
|
||||||
|
let max_offset = selected.saturating_sub(self.scroll_padding);
|
||||||
|
let global_max_offset = self.n_rows.saturating_sub(self.max_n_rows_to_display);
|
||||||
|
|
||||||
|
self.offset = self
|
||||||
|
.offset
|
||||||
|
.max(min_offset)
|
||||||
|
.min(max_offset)
|
||||||
|
.min(global_max_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn selected(&self) -> Option<usize> {
|
||||||
|
self.selected
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_selected(&mut self, selected: usize) {
|
||||||
|
self.selected = Some(selected);
|
||||||
|
self.update_offset();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_next(&mut self) {
|
||||||
|
if let Some(selected) = self.selected {
|
||||||
|
self.set_selected((selected + 1).min(self.n_rows - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_previous(&mut self) {
|
||||||
|
if let Some(selected) = self.selected {
|
||||||
|
self.set_selected(selected.saturating_sub(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_first(&mut self) {
|
||||||
|
if self.n_rows > 0 {
|
||||||
|
self.set_selected(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_last(&mut self) {
|
||||||
|
if self.n_rows > 0 {
|
||||||
|
self.set_selected(self.n_rows - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_n_rows(&mut self, n_rows: usize) {
|
||||||
|
self.n_rows = n_rows;
|
||||||
|
|
||||||
|
if self.n_rows == 0 {
|
||||||
|
self.selected = None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_selected(self.selected.map_or(0, |selected| selected.min(n_rows - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn update_scroll_padding(&mut self) {
|
||||||
|
self.scroll_padding = (self.max_n_rows_to_display / 4).min(self.max_scroll_padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn max_n_rows_to_display(&self) -> usize {
|
||||||
|
self.max_n_rows_to_display
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_max_n_rows_to_display(&mut self, max_n_rows_to_display: usize) {
|
||||||
|
self.max_n_rows_to_display = max_n_rows_to_display;
|
||||||
|
self.update_scroll_padding();
|
||||||
|
self.update_offset();
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,8 @@ use crate::{
|
||||||
MAX_EXERCISE_NAME_LEN,
|
MAX_EXERCISE_NAME_LEN,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_SCROLL_PADDING: usize = 5;
|
use super::scroll_state::ScrollState;
|
||||||
|
|
||||||
// +1 for column padding.
|
// +1 for column padding.
|
||||||
const SPACE: &[u8] = &[b' '; MAX_EXERCISE_NAME_LEN + 1];
|
const SPACE: &[u8] = &[b' '; MAX_EXERCISE_NAME_LEN + 1];
|
||||||
|
|
||||||
|
@ -39,19 +40,14 @@ pub struct ListState<'a> {
|
||||||
/// Footer message to be displayed if not empty.
|
/// Footer message to be displayed if not empty.
|
||||||
pub message: String,
|
pub message: String,
|
||||||
app_state: &'a mut AppState,
|
app_state: &'a mut AppState,
|
||||||
|
scroll_state: ScrollState,
|
||||||
name_col_width: usize,
|
name_col_width: usize,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
n_rows_with_filter: usize,
|
|
||||||
/// Selected row out of the filtered ones.
|
|
||||||
selected_row: Option<usize>,
|
|
||||||
row_offset: usize,
|
|
||||||
term_width: u16,
|
term_width: u16,
|
||||||
term_height: u16,
|
term_height: u16,
|
||||||
separator_line: Vec<u8>,
|
separator_line: Vec<u8>,
|
||||||
narrow_term: bool,
|
narrow_term: bool,
|
||||||
show_footer: bool,
|
show_footer: bool,
|
||||||
max_n_rows_to_display: usize,
|
|
||||||
scroll_padding: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ListState<'a> {
|
impl<'a> ListState<'a> {
|
||||||
|
@ -70,50 +66,29 @@ impl<'a> ListState<'a> {
|
||||||
let n_rows_with_filter = app_state.exercises().len();
|
let n_rows_with_filter = app_state.exercises().len();
|
||||||
let selected = app_state.current_exercise_ind();
|
let selected = app_state.current_exercise_ind();
|
||||||
|
|
||||||
|
let (width, height) = terminal::size()?;
|
||||||
|
let scroll_state = ScrollState::new(n_rows_with_filter, Some(selected), 5);
|
||||||
|
|
||||||
let mut slf = Self {
|
let mut slf = Self {
|
||||||
message: String::with_capacity(128),
|
message: String::with_capacity(128),
|
||||||
app_state,
|
app_state,
|
||||||
|
scroll_state,
|
||||||
name_col_width,
|
name_col_width,
|
||||||
filter,
|
filter,
|
||||||
n_rows_with_filter,
|
|
||||||
selected_row: Some(selected),
|
|
||||||
row_offset: selected.saturating_sub(MAX_SCROLL_PADDING),
|
|
||||||
// Set by `set_term_size`
|
// Set by `set_term_size`
|
||||||
term_width: 0,
|
term_width: 0,
|
||||||
term_height: 0,
|
term_height: 0,
|
||||||
separator_line: Vec::new(),
|
separator_line: Vec::new(),
|
||||||
narrow_term: false,
|
narrow_term: false,
|
||||||
show_footer: true,
|
show_footer: true,
|
||||||
max_n_rows_to_display: 0,
|
|
||||||
scroll_padding: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let (width, height) = terminal::size()?;
|
|
||||||
slf.set_term_size(width, height);
|
slf.set_term_size(width, height);
|
||||||
slf.draw(stdout)?;
|
slf.draw(stdout)?;
|
||||||
|
|
||||||
Ok(slf)
|
Ok(slf)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_offset(&mut self) {
|
|
||||||
let Some(selected) = self.selected_row else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let min_offset = (selected + self.scroll_padding)
|
|
||||||
.saturating_sub(self.max_n_rows_to_display.saturating_sub(1));
|
|
||||||
let max_offset = selected.saturating_sub(self.scroll_padding);
|
|
||||||
let global_max_offset = self
|
|
||||||
.n_rows_with_filter
|
|
||||||
.saturating_sub(self.max_n_rows_to_display);
|
|
||||||
|
|
||||||
self.row_offset = self
|
|
||||||
.row_offset
|
|
||||||
.max(min_offset)
|
|
||||||
.min(max_offset)
|
|
||||||
.min(global_max_offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_term_size(&mut self, width: u16, height: u16) {
|
pub fn set_term_size(&mut self, width: u16, height: u16) {
|
||||||
self.term_width = width;
|
self.term_width = width;
|
||||||
self.term_height = height;
|
self.term_height = height;
|
||||||
|
@ -124,7 +99,7 @@ impl<'a> ListState<'a> {
|
||||||
|
|
||||||
let wide_help_footer_width = 95;
|
let wide_help_footer_width = 95;
|
||||||
// The help footer is shorter when nothing is selected.
|
// The help footer is shorter when nothing is selected.
|
||||||
self.narrow_term = width < wide_help_footer_width && self.selected_row.is_some();
|
self.narrow_term = width < wide_help_footer_width && self.scroll_state.selected().is_some();
|
||||||
|
|
||||||
let header_height = 1;
|
let header_height = 1;
|
||||||
// 2 separator, 1 progress bar, 1-2 footer message.
|
// 2 separator, 1 progress bar, 1-2 footer message.
|
||||||
|
@ -135,13 +110,10 @@ impl<'a> ListState<'a> {
|
||||||
self.separator_line = "─".as_bytes().repeat(width as usize);
|
self.separator_line = "─".as_bytes().repeat(width as usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.max_n_rows_to_display = height
|
self.scroll_state.set_max_n_rows_to_display(
|
||||||
.saturating_sub(header_height + u16::from(self.show_footer) * footer_height)
|
height.saturating_sub(header_height + u16::from(self.show_footer) * footer_height)
|
||||||
as usize;
|
as usize,
|
||||||
|
);
|
||||||
self.scroll_padding = (self.max_n_rows_to_display / 4).min(MAX_SCROLL_PADDING);
|
|
||||||
|
|
||||||
self.update_offset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_rows(
|
fn draw_rows(
|
||||||
|
@ -150,15 +122,16 @@ impl<'a> ListState<'a> {
|
||||||
filtered_exercises: impl Iterator<Item = (usize, &'a Exercise)>,
|
filtered_exercises: impl Iterator<Item = (usize, &'a Exercise)>,
|
||||||
) -> io::Result<usize> {
|
) -> io::Result<usize> {
|
||||||
let current_exercise_ind = self.app_state.current_exercise_ind();
|
let current_exercise_ind = self.app_state.current_exercise_ind();
|
||||||
|
let row_offset = self.scroll_state.offset();
|
||||||
let mut n_displayed_rows = 0;
|
let mut n_displayed_rows = 0;
|
||||||
|
|
||||||
for (exercise_ind, exercise) in filtered_exercises
|
for (exercise_ind, exercise) in filtered_exercises
|
||||||
.skip(self.row_offset)
|
.skip(row_offset)
|
||||||
.take(self.max_n_rows_to_display)
|
.take(self.scroll_state.max_n_rows_to_display())
|
||||||
{
|
{
|
||||||
let mut writer = MaxLenWriter::new(stdout, self.term_width as usize);
|
let mut writer = MaxLenWriter::new(stdout, self.term_width as usize);
|
||||||
|
|
||||||
if self.selected_row == Some(self.row_offset + n_displayed_rows) {
|
if self.scroll_state.selected() == Some(row_offset + n_displayed_rows) {
|
||||||
writer.stdout.queue(SetBackgroundColor(Color::Rgb {
|
writer.stdout.queue(SetBackgroundColor(Color::Rgb {
|
||||||
r: 40,
|
r: 40,
|
||||||
g: 40,
|
g: 40,
|
||||||
|
@ -225,7 +198,7 @@ impl<'a> ListState<'a> {
|
||||||
Filter::None => self.draw_rows(stdout, iter)?,
|
Filter::None => self.draw_rows(stdout, iter)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
for _ in 0..self.max_n_rows_to_display - n_displayed_rows {
|
for _ in 0..self.scroll_state.max_n_rows_to_display() - n_displayed_rows {
|
||||||
next_ln(stdout)?;
|
next_ln(stdout)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +220,7 @@ impl<'a> ListState<'a> {
|
||||||
let mut writer = MaxLenWriter::new(stdout, self.term_width as usize);
|
let mut writer = MaxLenWriter::new(stdout, self.term_width as usize);
|
||||||
if self.message.is_empty() {
|
if self.message.is_empty() {
|
||||||
// Help footer message
|
// Help footer message
|
||||||
if self.selected_row.is_some() {
|
if self.scroll_state.selected().is_some() {
|
||||||
writer.write_str("↓/j ↑/k home/g end/G | <c>ontinue at | <r>eset exercise")?;
|
writer.write_str("↓/j ↑/k home/g end/G | <c>ontinue at | <r>eset exercise")?;
|
||||||
if self.narrow_term {
|
if self.narrow_term {
|
||||||
next_ln(stdout)?;
|
next_ln(stdout)?;
|
||||||
|
@ -298,13 +271,8 @@ impl<'a> ListState<'a> {
|
||||||
stdout.queue(EndSynchronizedUpdate)?.flush()
|
stdout.queue(EndSynchronizedUpdate)?.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_selected(&mut self, selected: usize) {
|
|
||||||
self.selected_row = Some(selected);
|
|
||||||
self.update_offset();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_rows(&mut self) {
|
fn update_rows(&mut self) {
|
||||||
self.n_rows_with_filter = match self.filter {
|
let n_rows = match self.filter {
|
||||||
Filter::Done => self
|
Filter::Done => self
|
||||||
.app_state
|
.app_state
|
||||||
.exercises()
|
.exercises()
|
||||||
|
@ -320,15 +288,7 @@ impl<'a> ListState<'a> {
|
||||||
Filter::None => self.app_state.exercises().len(),
|
Filter::None => self.app_state.exercises().len(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.n_rows_with_filter == 0 {
|
self.scroll_state.set_n_rows(n_rows);
|
||||||
self.selected_row = None;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.set_selected(
|
|
||||||
self.selected_row
|
|
||||||
.map_or(0, |selected| selected.min(self.n_rows_with_filter - 1)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -341,28 +301,24 @@ impl<'a> ListState<'a> {
|
||||||
self.update_rows();
|
self.update_rows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn select_next(&mut self) {
|
pub fn select_next(&mut self) {
|
||||||
if let Some(selected) = self.selected_row {
|
self.scroll_state.select_next();
|
||||||
self.set_selected((selected + 1).min(self.n_rows_with_filter - 1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn select_previous(&mut self) {
|
pub fn select_previous(&mut self) {
|
||||||
if let Some(selected) = self.selected_row {
|
self.scroll_state.select_previous();
|
||||||
self.set_selected(selected.saturating_sub(1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn select_first(&mut self) {
|
pub fn select_first(&mut self) {
|
||||||
if self.n_rows_with_filter > 0 {
|
self.scroll_state.select_first();
|
||||||
self.set_selected(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn select_last(&mut self) {
|
pub fn select_last(&mut self) {
|
||||||
if self.n_rows_with_filter > 0 {
|
self.scroll_state.select_last();
|
||||||
self.set_selected(self.n_rows_with_filter - 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_to_exercise_ind(&self, selected: usize) -> Result<usize> {
|
fn selected_to_exercise_ind(&self, selected: usize) -> Result<usize> {
|
||||||
|
@ -390,7 +346,7 @@ impl<'a> ListState<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_selected(&mut self) -> Result<()> {
|
pub fn reset_selected(&mut self) -> Result<()> {
|
||||||
let Some(selected) = self.selected_row else {
|
let Some(selected) = self.scroll_state.selected() else {
|
||||||
self.message.push_str("Nothing selected to reset!");
|
self.message.push_str("Nothing selected to reset!");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
@ -408,7 +364,7 @@ impl<'a> ListState<'a> {
|
||||||
|
|
||||||
// Return `true` if there was something to select.
|
// Return `true` if there was something to select.
|
||||||
pub fn selected_to_current_exercise(&mut self) -> Result<bool> {
|
pub fn selected_to_current_exercise(&mut self) -> Result<bool> {
|
||||||
let Some(selected) = self.selected_row else {
|
let Some(selected) = self.scroll_state.selected() else {
|
||||||
self.message.push_str("Nothing selected to continue at!");
|
self.message.push_str("Nothing selected to continue at!");
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
|
|
|
@ -71,9 +71,7 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Some(Subcommands::Init) => {
|
Some(Subcommands::Init) => return init::init().context("Initialization failed"),
|
||||||
return init::init().context("Initialization failed");
|
|
||||||
}
|
|
||||||
Some(Subcommands::Dev(dev_command)) => return dev_command.run(),
|
Some(Subcommands::Dev(dev_command)) => return dev_command.run(),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,8 +153,7 @@ pub fn clear_terminal(stdout: &mut StdoutLock) -> io::Result<()> {
|
||||||
pub fn press_enter_prompt(stdout: &mut StdoutLock) -> io::Result<()> {
|
pub fn press_enter_prompt(stdout: &mut StdoutLock) -> io::Result<()> {
|
||||||
stdout.flush()?;
|
stdout.flush()?;
|
||||||
io::stdin().lock().read_until(b'\n', &mut Vec::new())?;
|
io::stdin().lock().read_until(b'\n', &mut Vec::new())?;
|
||||||
stdout.write_all(b"\n")?;
|
stdout.write_all(b"\n")
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn terminal_file_link<'a>(
|
pub fn terminal_file_link<'a>(
|
||||||
|
|
Loading…
Reference in a new issue