Compare commits

..

8 commits

Author SHA1 Message Date
mo8it e6cb104294 chore: Release 2024-11-11 15:51:27 +01:00
mo8it 410eb69d25 Remove "chore: " from the commit message of releases 2024-11-11 15:49:50 +01:00
mo8it 243cf5f261 Update CHANGELOG 2024-11-11 15:49:24 +01:00
mo8it eff2ce8a23 Ignore input while checking all exercises in watch mode 2024-11-11 14:55:58 +01:00
mo8it fd33c29b26 Test with MSRV before release 2024-11-11 14:43:51 +01:00
mo8it f49164e69b Fix typo 2024-11-11 14:43:38 +01:00
mo8it 9bc7bbe4b4 Update deps 2024-11-11 14:35:22 +01:00
mo8it 46ad25f925 Fix contrast in terminals with a light theme 2024-11-11 14:34:33 +01:00
6 changed files with 99 additions and 46 deletions

View file

@ -1,3 +1,34 @@
<a name="6.4.0"></a>
## 6.4.0 (2024-11-11)
### Added
- The list of exercises is now searchable by pressing `s` or `/` 🔍️ (thanks to [@frroossst](https://github.com/frroossst))
- New option `c` in the prompt to manually check all exercises ✅ (thanks to [@Nahor](https://github.com/Nahor))
- New command `check-all` to manually check all exercises ✅ (thanks to [@Nahor](https://github.com/Nahor))
- Addictive animation for showing the progress of checking all exercises. A nice showcase of parallelism in Rust ✨
- New option `x` in the prompt to reset the file of the current exercise 🔄
- Allow `dead_code` for all exercises and solutions ⚰️ (thanks to [@huss4in](https://github.com/huss4in))
- Pause input while running an exercise to avoid unexpected prompt interactions ⏸️
- Limit the maximum number of exercises to 999. Any third-party exercises willing to reach that limit? 🔝
### Changed
- `enums3`: Remove redundant enum definition task (thanks to [@senekor](https://github.com/senekor))
- `if2`: Make the exercise less confusing by avoiding "fizz", "fuzz", "foo", "bar" and "baz" (thanks to [@senekor](https://github.com/senekor))
- `hashmap3`: Use the method `Entry::or_default`.
- Update the state of all exercises when checking all of them (thanks to [@Nahor](https://github.com/Nahor))
- The main prompt doesn't need a confirmation with ENTER on Unix-like systems anymore.
- No more jumping back to a previous exercise when its file is changed. Use the list to jump between exercises.
- Dump the solution file after an exercise is done even if the solution's directory doesn't exist.
- Rework the footer in the list.
- Optimize the file watcher.
### Fixed
- Fix bad contrast in the list on terminals with a light theme.
<a name="6.3.0"></a> <a name="6.3.0"></a>
## 6.3.0 (2024-08-29) ## 6.3.0 (2024-08-29)
@ -113,7 +144,7 @@ You can read about the motivations of this change in [this issue](https://github
### List mode ### List mode
A list mode was added using [Ratatui](https://ratatui.rs). A new list mode was added!
You can enter it by entering `l` in the watch mode. You can enter it by entering `l` in the watch mode.
It offers the following features: It offers the following features:
@ -814,7 +845,7 @@ Then follow the link to the guide about [third-party exercises](THIRD_PARTY_EXER
#### Bug Fixes #### Bug Fixes
- Update deps to version compatable with aarch64-pc-windows (#263) ([19a93428](https://github.com/rust-lang/rustlings/commit/19a93428b3c73d994292671f829bdc8e5b7b3401)) - Update deps to version compatible with aarch64-pc-windows (#263) ([19a93428](https://github.com/rust-lang/rustlings/commit/19a93428b3c73d994292671f829bdc8e5b7b3401))
- **docs:** - **docs:**
- Added a necessary step to Windows installation process (#242) ([3906efcd](https://github.com/rust-lang/rustlings/commit/3906efcd52a004047b460ed548037093de3f523f)) - Added a necessary step to Windows installation process (#242) ([3906efcd](https://github.com/rust-lang/rustlings/commit/3906efcd52a004047b460ed548037093de3f523f))
- Fixed mangled sentence from book; edited for clarity (#266) ([ade52ff](https://github.com/rust-lang/rustlings/commit/ade52ffb739987287ddd5705944c8777705faed9)) - Fixed mangled sentence from book; edited for clarity (#266) ([ade52ff](https://github.com/rust-lang/rustlings/commit/ade52ffb739987287ddd5705944c8777705faed9))

40
Cargo.lock generated
View file

@ -4,9 +4,9 @@ version = 3
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.17" version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
@ -19,9 +19,9 @@ dependencies = [
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.9" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
@ -53,9 +53,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.91" version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
@ -170,9 +170,9 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.1.1" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
[[package]] [[package]]
name = "filetime" name = "filetime"
@ -197,9 +197,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.0" version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
[[package]] [[package]]
name = "heck" name = "heck"
@ -286,9 +286,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.161" version = "0.2.162"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
[[package]] [[package]]
name = "libredox" name = "libredox"
@ -438,9 +438,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.38" version = "0.38.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"errno", "errno",
@ -451,7 +451,7 @@ dependencies = [
[[package]] [[package]]
name = "rustlings" name = "rustlings"
version = "6.3.0" version = "6.4.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -468,7 +468,7 @@ dependencies = [
[[package]] [[package]]
name = "rustlings-macros" name = "rustlings-macros"
version = "6.3.0" version = "6.4.0"
dependencies = [ dependencies = [
"quote", "quote",
"serde", "serde",
@ -581,9 +581,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.85" version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -592,9 +592,9 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.13.0" version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",

View file

@ -6,7 +6,7 @@ exclude = [
] ]
[workspace.package] [workspace.package]
version = "6.3.0" version = "6.4.0"
authors = [ authors = [
"Mo Bitar <mo8it@proton.me>", # https://github.com/mo8it "Mo Bitar <mo8it@proton.me>", # https://github.com/mo8it
"Liv <mokou@fastmail.com>", # https://github.com/shadows-withal "Liv <mokou@fastmail.com>", # https://github.com/shadows-withal
@ -46,12 +46,12 @@ include = [
] ]
[dependencies] [dependencies]
anyhow = "1.0.91" anyhow = "1.0.93"
clap = { version = "4.5.20", features = ["derive"] } clap = { version = "4.5.20", features = ["derive"] }
crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] } crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] }
notify = "7.0.0" notify = "7.0.0"
os_pipe = "1.2.1" os_pipe = "1.2.1"
rustlings-macros = { path = "rustlings-macros", version = "=6.3.0" } rustlings-macros = { path = "rustlings-macros", version = "=6.4.0" }
serde_json = "1.0.132" serde_json = "1.0.132"
serde.workspace = true serde.workspace = true
toml_edit.workspace = true toml_edit.workspace = true
@ -60,7 +60,7 @@ toml_edit.workspace = true
rustix = { version = "0.38.38", default-features = false, features = ["std", "stdio", "termios"] } rustix = { version = "0.38.38", default-features = false, features = ["std", "stdio", "termios"] }
[dev-dependencies] [dev-dependencies]
tempfile = "3.13.0" tempfile = "3.14.0"
[profile.release] [profile.release]
panic = "abort" panic = "abort"
@ -70,6 +70,7 @@ panic = "abort"
[package.metadata.release] [package.metadata.release]
pre-release-hook = ["./release-hook.sh"] pre-release-hook = ["./release-hook.sh"]
pre-release-commit-message = "Release 🎉"
[workspace.lints.rust] [workspace.lints.rust]
unsafe_code = "forbid" unsafe_code = "forbid"

View file

@ -11,3 +11,6 @@ cargo clippy -- --deny warnings
cargo fmt --all --check cargo fmt --all --check
cargo test --workspace --all-targets cargo test --workspace --all-targets
cargo run -- dev check --require-solutions cargo run -- dev check --require-solutions
# MSRV
cargo +1.80 run -- dev check --require-solutions

View file

@ -1,7 +1,9 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use crossterm::{ use crossterm::{
cursor::{MoveTo, MoveToNextLine}, cursor::{MoveTo, MoveToNextLine},
style::{Attribute, Color, ResetColor, SetAttribute, SetBackgroundColor, SetForegroundColor}, style::{
Attribute, Attributes, Color, ResetColor, SetAttribute, SetAttributes, SetForegroundColor,
},
terminal::{self, BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate}, terminal::{self, BeginSynchronizedUpdate, Clear, ClearType, EndSynchronizedUpdate},
QueueableCommand, QueueableCommand,
}; };
@ -19,6 +21,9 @@ use crate::{
use super::scroll_state::ScrollState; use super::scroll_state::ScrollState;
const COL_SPACING: usize = 2; const COL_SPACING: usize = 2;
const SELECTED_ROW_ATTRIBUTES: Attributes = Attributes::none()
.with(Attribute::Reverse)
.with(Attribute::Bold);
fn next_ln(stdout: &mut StdoutLock) -> io::Result<()> { fn next_ln(stdout: &mut StdoutLock) -> io::Result<()> {
stdout stdout
@ -41,6 +46,7 @@ pub struct ListState<'a> {
app_state: &'a mut AppState, app_state: &'a mut AppState,
scroll_state: ScrollState, scroll_state: ScrollState,
name_col_padding: Vec<u8>, name_col_padding: Vec<u8>,
path_col_padding: Vec<u8>,
filter: Filter, filter: Filter,
term_width: u16, term_width: u16,
term_height: u16, term_height: u16,
@ -52,13 +58,18 @@ impl<'a> ListState<'a> {
stdout.queue(Clear(ClearType::All))?; stdout.queue(Clear(ClearType::All))?;
let name_col_title_len = 4; let name_col_title_len = 4;
let name_col_width = app_state let path_col_title_len = 4;
.exercises() let (name_col_width, path_col_width) = app_state.exercises().iter().fold(
.iter() (name_col_title_len, path_col_title_len),
.map(|exercise| exercise.name.len()) |(name_col_width, path_col_width), exercise| {
.max() (
.map_or(name_col_title_len, |max| max.max(name_col_title_len)); name_col_width.max(exercise.name.len()),
path_col_width.max(exercise.path.len()),
)
},
);
let name_col_padding = vec![b' '; name_col_width + COL_SPACING]; let name_col_padding = vec![b' '; name_col_width + COL_SPACING];
let path_col_padding = vec![b' '; path_col_width];
let filter = Filter::None; let filter = Filter::None;
let n_rows_with_filter = app_state.exercises().len(); let n_rows_with_filter = app_state.exercises().len();
@ -73,6 +84,7 @@ impl<'a> ListState<'a> {
app_state, app_state,
scroll_state, scroll_state,
name_col_padding, name_col_padding,
path_col_padding,
filter, filter,
// Set by `set_term_size` // Set by `set_term_size`
term_width: 0, term_width: 0,
@ -105,7 +117,7 @@ impl<'a> ListState<'a> {
); );
} }
fn draw_exericse_name(&self, writer: &mut MaxLenWriter, exercise: &Exercise) -> io::Result<()> { fn draw_exercise_name(&self, writer: &mut MaxLenWriter, exercise: &Exercise) -> io::Result<()> {
if !self.search_query.is_empty() { if !self.search_query.is_empty() {
if let Some((pre_highlight, highlight, post_highlight)) = exercise if let Some((pre_highlight, highlight, post_highlight)) = exercise
.name .name
@ -119,7 +131,7 @@ impl<'a> ListState<'a> {
writer.write_str(pre_highlight)?; writer.write_str(pre_highlight)?;
writer.stdout.queue(SetForegroundColor(Color::Magenta))?; writer.stdout.queue(SetForegroundColor(Color::Magenta))?;
writer.write_str(highlight)?; writer.write_str(highlight)?;
writer.stdout.queue(ResetColor)?; writer.stdout.queue(SetForegroundColor(Color::Reset))?;
return writer.write_str(post_highlight); return writer.write_str(post_highlight);
} }
} }
@ -143,14 +155,12 @@ 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.scroll_state.selected() == Some(row_offset + n_displayed_rows) { if self.scroll_state.selected() == Some(row_offset + n_displayed_rows) {
writer.stdout.queue(SetBackgroundColor(Color::Rgb {
r: 40,
g: 40,
b: 40,
}))?;
// The crab emoji has the width of two ascii chars. // The crab emoji has the width of two ascii chars.
writer.add_to_len(2); writer.add_to_len(2);
writer.stdout.write_all("🦀".as_bytes())?; writer.stdout.write_all("🦀".as_bytes())?;
writer
.stdout
.queue(SetAttributes(SELECTED_ROW_ATTRIBUTES))?;
} else { } else {
writer.write_ascii(b" ")?; writer.write_ascii(b" ")?;
} }
@ -170,8 +180,9 @@ impl<'a> ListState<'a> {
writer.write_ascii(b"PENDING")?; writer.write_ascii(b"PENDING")?;
} }
writer.stdout.queue(SetForegroundColor(Color::Reset))?; writer.stdout.queue(SetForegroundColor(Color::Reset))?;
writer.write_ascii(b" ")?;
self.draw_exericse_name(&mut writer, exercise)?; self.draw_exercise_name(&mut writer, exercise)?;
writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?; writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?;
@ -183,6 +194,8 @@ impl<'a> ListState<'a> {
exercise.terminal_file_link(&mut writer)?; exercise.terminal_file_link(&mut writer)?;
} }
writer.write_ascii(&self.path_col_padding[exercise.path.len()..])?;
next_ln(stdout)?; next_ln(stdout)?;
stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
n_displayed_rows += 1; n_displayed_rows += 1;

View file

@ -20,6 +20,10 @@ use crate::{
use super::{terminal_event::terminal_event_handler, InputPauseGuard, WatchEvent}; use super::{terminal_event::terminal_event_handler, InputPauseGuard, WatchEvent};
const HEADING_ATTRIBUTES: Attributes = Attributes::none()
.with(Attribute::Bold)
.with(Attribute::Underlined);
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
enum DoneStatus { enum DoneStatus {
DoneWithSolution(String), DoneWithSolution(String),
@ -209,9 +213,7 @@ impl<'a> WatchState<'a> {
if self.show_hint { if self.show_hint {
stdout stdout
.queue(SetAttributes( .queue(SetAttributes(HEADING_ATTRIBUTES))?
Attributes::from(Attribute::Bold).with(Attribute::Underlined),
))?
.queue(SetForegroundColor(Color::Cyan))?; .queue(SetForegroundColor(Color::Cyan))?;
stdout.write_all(b"Hint")?; stdout.write_all(b"Hint")?;
stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
@ -267,6 +269,9 @@ impl<'a> WatchState<'a> {
} }
pub fn check_all_exercises(&mut self, stdout: &mut StdoutLock) -> Result<ExercisesProgress> { pub fn check_all_exercises(&mut self, stdout: &mut StdoutLock) -> Result<ExercisesProgress> {
// Ignore any input until checking all exercises is done.
let _input_pause_guard = InputPauseGuard::scoped_pause();
if let Some(first_pending_exercise_ind) = self.app_state.check_all_exercises(stdout)? { if let Some(first_pending_exercise_ind) = self.app_state.check_all_exercises(stdout)? {
// Only change exercise if the current one is done. // Only change exercise if the current one is done.
if self.app_state.current_exercise().done { if self.app_state.current_exercise().done {