From 278edc0b962cf03d5eef214063d720279e72953a Mon Sep 17 00:00:00 2001 From: zeonzip <96481337+zeonzip@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:24:09 +0200 Subject: [PATCH 01/10] Clarify how to find return type of error4 --- exercises/13_error_handling/errors4.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/13_error_handling/errors4.rs b/exercises/13_error_handling/errors4.rs index ba01e54bf5..144fce7b22 100644 --- a/exercises/13_error_handling/errors4.rs +++ b/exercises/13_error_handling/errors4.rs @@ -10,6 +10,7 @@ struct PositiveNonzeroInteger(u64); impl PositiveNonzeroInteger { fn new(value: i64) -> Result { // TODO: This function shouldn't always return an `Ok`. + // Read the tests below to clarify what should be returned. Ok(Self(value as u64)) } } From 3a2fe2c39472f780edb519f040427b9380617332 Mon Sep 17 00:00:00 2001 From: Hud Miller Date: Fri, 18 Jul 2025 13:02:56 -0500 Subject: [PATCH 02/10] Fix incorrect book chapter number --- exercises/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/README.md b/exercises/README.md index 237f2f1edc..86b35916bc 100644 --- a/exercises/README.md +++ b/exercises/README.md @@ -22,6 +22,6 @@ | iterators | §13.2-4 | | smart_pointers | §15, §16.3 | | threads | §16.1-3 | -| macros | §19.5 | +| macros | §20.5 | | clippy | §21.4 | | conversions | n/a | From 4f9f0907c3a15c9f26d6675e65832a382419e8a2 Mon Sep 17 00:00:00 2001 From: deafloo Date: Mon, 21 Jul 2025 12:13:32 +0200 Subject: [PATCH 03/10] Add positive tests This prevents solving the exercise without a real IntegerOverflow --- exercises/18_iterators/iterators3.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exercises/18_iterators/iterators3.rs b/exercises/18_iterators/iterators3.rs index 6b1eca1734..dce09055dd 100644 --- a/exercises/18_iterators/iterators3.rs +++ b/exercises/18_iterators/iterators3.rs @@ -39,6 +39,8 @@ mod tests { #[test] fn test_success() { assert_eq!(divide(81, 9), Ok(9)); + assert_eq!(divide(81, -1), Ok(-81)); + assert_eq!(divide(i64::MIN, i64::MIN), Ok(1)); } #[test] From 2d1d531550afaac36dbf4d15c383bc0e1cbf248d Mon Sep 17 00:00:00 2001 From: mo8it Date: Thu, 21 Aug 2025 22:43:46 +0200 Subject: [PATCH 04/10] Fix file links in VS Code --- src/app_state.rs | 12 ++++++------ src/exercise.rs | 38 +++++++++++++++++++++++++------------- src/list/state.rs | 8 +------- src/main.rs | 2 +- src/run.rs | 6 +++--- src/term.rs | 29 +++++++++++++++++++---------- src/watch/state.rs | 4 ++-- 7 files changed, 57 insertions(+), 42 deletions(-) diff --git a/src/app_state.rs b/src/app_state.rs index f3f348133e..d654d0425d 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -60,8 +60,7 @@ pub struct AppState { file_buf: Vec, official_exercises: bool, cmd_runner: CmdRunner, - // Running in VS Code. - vs_code: bool, + emit_file_links: bool, } impl AppState { @@ -181,7 +180,8 @@ impl AppState { file_buf, official_exercises: !Path::new("info.toml").exists(), cmd_runner, - vs_code: env::var_os("TERM_PROGRAM").is_some_and(|v| v == "vscode"), + // VS Code has its own file link handling + emit_file_links: env::var_os("TERM_PROGRAM").is_none_or(|v| v != "vscode"), }; Ok((slf, state_file_status)) @@ -218,8 +218,8 @@ impl AppState { } #[inline] - pub fn vs_code(&self) -> bool { - self.vs_code + pub fn emit_file_links(&self) -> bool { + self.emit_file_links } // Write the state file. @@ -621,7 +621,7 @@ mod tests { file_buf: Vec::new(), official_exercises: true, cmd_runner: CmdRunner::build().unwrap(), - vs_code: false, + emit_file_links: true, }; let mut assert = |done: [bool; 3], expected: [Option; 3]| { diff --git a/src/exercise.rs b/src/exercise.rs index fdfbc4f6ea..6f517bee7c 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -7,22 +7,28 @@ use std::io::{self, StdoutLock, Write}; use crate::{ cmd::CmdRunner, - term::{self, CountedWrite, terminal_file_link, write_ansi}, + term::{self, CountedWrite, file_path, terminal_file_link, write_ansi}, }; /// The initial capacity of the output buffer. pub const OUTPUT_CAPACITY: usize = 1 << 14; -pub fn solution_link_line(stdout: &mut StdoutLock, solution_path: &str) -> io::Result<()> { +pub fn solution_link_line( + stdout: &mut StdoutLock, + solution_path: &str, + emit_file_links: bool, +) -> io::Result<()> { stdout.queue(SetAttribute(Attribute::Bold))?; stdout.write_all(b"Solution")?; stdout.queue(ResetColor)?; stdout.write_all(b" for comparison: ")?; - if let Some(canonical_path) = term::canonicalize(solution_path) { - terminal_file_link(stdout, solution_path, &canonical_path, Color::Cyan)?; - } else { - stdout.write_all(solution_path.as_bytes())?; - } + file_path(stdout, Color::Cyan, |writer| { + if emit_file_links && let Some(canonical_path) = term::canonicalize(solution_path) { + terminal_file_link(writer, solution_path, &canonical_path) + } else { + writer.stdout().write_all(solution_path.as_bytes()) + } + })?; stdout.write_all(b"\n") } @@ -72,12 +78,18 @@ pub struct Exercise { } impl Exercise { - pub fn terminal_file_link<'a>(&self, writer: &mut impl CountedWrite<'a>) -> io::Result<()> { - if let Some(canonical_path) = self.canonical_path.as_deref() { - return terminal_file_link(writer, self.path, canonical_path, Color::Blue); - } - - writer.write_str(self.path) + pub fn terminal_file_link<'a>( + &self, + writer: &mut impl CountedWrite<'a>, + emit_file_links: bool, + ) -> io::Result<()> { + file_path(writer, Color::Blue, |writer| { + if emit_file_links && let Some(canonical_path) = self.canonical_path.as_deref() { + terminal_file_link(writer, self.path, canonical_path) + } else { + writer.write_str(self.path) + } + }) } } diff --git a/src/list/state.rs b/src/list/state.rs index ae65ec2be9..50d06be9cd 100644 --- a/src/list/state.rs +++ b/src/list/state.rs @@ -186,13 +186,7 @@ impl<'a> ListState<'a> { writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?; - // The list links aren't shown correctly in VS Code on Windows. - // But VS Code shows its own links anyway. - if self.app_state.vs_code() { - writer.write_str(exercise.path)?; - } else { - exercise.terminal_file_link(&mut writer)?; - } + exercise.terminal_file_link(&mut writer, self.app_state.emit_file_links())?; writer.write_ascii(&self.path_col_padding[exercise.path.len()..])?; diff --git a/src/main.rs b/src/main.rs index 29de56b3d2..ffd2dfa75b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -167,7 +167,7 @@ fn main() -> Result { } app_state .current_exercise() - .terminal_file_link(&mut stdout)?; + .terminal_file_link(&mut stdout, app_state.emit_file_links())?; stdout.write_all(b"\n")?; return Ok(ExitCode::FAILURE); diff --git a/src/run.rs b/src/run.rs index 6f4f099b47..b473fc2062 100644 --- a/src/run.rs +++ b/src/run.rs @@ -27,7 +27,7 @@ pub fn run(app_state: &mut AppState) -> Result { stdout.write_all(b"Ran ")?; app_state .current_exercise() - .terminal_file_link(&mut stdout)?; + .terminal_file_link(&mut stdout, app_state.emit_file_links())?; stdout.write_all(b" with errors\n")?; return Ok(ExitCode::FAILURE); @@ -41,7 +41,7 @@ pub fn run(app_state: &mut AppState) -> Result { if let Some(solution_path) = app_state.current_solution_path()? { stdout.write_all(b"\n")?; - solution_link_line(&mut stdout, &solution_path)?; + solution_link_line(&mut stdout, &solution_path, app_state.emit_file_links())?; stdout.write_all(b"\n")?; } @@ -50,7 +50,7 @@ pub fn run(app_state: &mut AppState) -> Result { stdout.write_all(b"Next exercise: ")?; app_state .current_exercise() - .terminal_file_link(&mut stdout)?; + .terminal_file_link(&mut stdout, app_state.emit_file_links())?; stdout.write_all(b"\n")?; } ExercisesProgress::AllDone => (), diff --git a/src/term.rs b/src/term.rs index b7dcd9f101..3d149b33e8 100644 --- a/src/term.rs +++ b/src/term.rs @@ -272,22 +272,18 @@ pub fn canonicalize(path: &str) -> Option { }) } -pub fn terminal_file_link<'a>( - writer: &mut impl CountedWrite<'a>, - path: &str, - canonical_path: &str, +pub fn file_path<'a, W: CountedWrite<'a>>( + writer: &mut W, color: Color, + f: impl FnOnce(&mut W) -> io::Result<()>, ) -> io::Result<()> { writer .stdout() .queue(SetForegroundColor(color))? .queue(SetAttribute(Attribute::Underlined))?; - writer.stdout().write_all(b"\x1b]8;;file://")?; - writer.stdout().write_all(canonical_path.as_bytes())?; - writer.stdout().write_all(b"\x1b\\")?; - // Only this part is visible. - writer.write_str(path)?; - writer.stdout().write_all(b"\x1b]8;;\x1b\\")?; + + f(writer)?; + writer .stdout() .queue(SetForegroundColor(Color::Reset))? @@ -296,6 +292,19 @@ pub fn terminal_file_link<'a>( Ok(()) } +pub fn terminal_file_link<'a>( + writer: &mut impl CountedWrite<'a>, + path: &str, + canonical_path: &str, +) -> io::Result<()> { + writer.stdout().write_all(b"\x1b]8;;file://")?; + writer.stdout().write_all(canonical_path.as_bytes())?; + writer.stdout().write_all(b"\x1b\\")?; + // Only this part is visible. + writer.write_str(path)?; + writer.stdout().write_all(b"\x1b]8;;\x1b\\") +} + pub fn write_ansi(output: &mut Vec, command: impl Command) { struct FmtWriter<'a>(&'a mut Vec); diff --git a/src/watch/state.rs b/src/watch/state.rs index 2413becd28..a92dd2d6d7 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -233,7 +233,7 @@ impl<'a> WatchState<'a> { stdout.write_all(b"\n")?; if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status { - solution_link_line(stdout, solution_path)?; + solution_link_line(stdout, solution_path, self.app_state.emit_file_links())?; } stdout.write_all( @@ -252,7 +252,7 @@ impl<'a> WatchState<'a> { stdout.write_all(b"\nCurrent exercise: ")?; self.app_state .current_exercise() - .terminal_file_link(stdout)?; + .terminal_file_link(stdout, self.app_state.emit_file_links())?; stdout.write_all(b"\n\n")?; self.show_prompt(stdout)?; From 208a5932165c2fcf74b1dbb602f9427cc302929e Mon Sep 17 00:00:00 2001 From: mo8it Date: Thu, 21 Aug 2025 22:08:54 +0200 Subject: [PATCH 05/10] Ready to release --- CHANGELOG.md | 14 ++++++++++++++ Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1dbb42bbe..32d95107cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,23 @@ ## Unreleased +## 6.5.0 (2025-08-21) + +### Added + +- Check that Clippy is installed before initialization + ### Changed - Upgrade to Rust edition 2024 - Raise the minimum supported Rust version to `1.87` +- Don't follow symlinks in the file watcher +- `dev new`: Don't add `.rustlings-state.txt` to `.gitignore` + +### Fixed + +- Fix file links in VS Code +- Fix error printing when the progress bar is shown +- `dev check`: Don't check formatting if there are no solution files ## 6.4.0 (2024-11-11) diff --git a/Cargo.lock b/Cargo.lock index a0c2f08051..c743dd7fb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,9 +78,9 @@ checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "clap" @@ -505,9 +505,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" dependencies = [ "itoa", "memchr", @@ -579,15 +579,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" dependencies = [ "fastrand", "getrandom", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -684,11 +684,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 56adbb5cb9..454f3b8018 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ toml.workspace = true rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] } [dev-dependencies] -tempfile = "3.19" +tempfile = "3.21" [profile.release] panic = "abort" From b6b94e3e96dc71099055336e0b742e981c730157 Mon Sep 17 00:00:00 2001 From: mo8it Date: Thu, 21 Aug 2025 23:34:45 +0200 Subject: [PATCH 06/10] Sync solution --- solutions/18_iterators/iterators3.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/solutions/18_iterators/iterators3.rs b/solutions/18_iterators/iterators3.rs index 11aa1ec8ce..1d5d67f29f 100644 --- a/solutions/18_iterators/iterators3.rs +++ b/solutions/18_iterators/iterators3.rs @@ -52,6 +52,8 @@ mod tests { #[test] fn test_success() { assert_eq!(divide(81, 9), Ok(9)); + assert_eq!(divide(81, -1), Ok(-81)); + assert_eq!(divide(i64::MIN, i64::MIN), Ok(1)); } #[test] From 628ef55337429c26df53f4bec823653d6944dddb Mon Sep 17 00:00:00 2001 From: mo8it Date: Thu, 21 Aug 2025 23:38:42 +0200 Subject: [PATCH 07/10] Fix Clippy chapter --- exercises/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/README.md b/exercises/README.md index 86b35916bc..1df5cc3757 100644 --- a/exercises/README.md +++ b/exercises/README.md @@ -23,5 +23,5 @@ | smart_pointers | §15, §16.3 | | threads | §16.1-3 | | macros | §20.5 | -| clippy | §21.4 | +| clippy | Appendix D | | conversions | n/a | From 295ad2e4bd41147bf10028800a82247c2c0048a4 Mon Sep 17 00:00:00 2001 From: mo8it Date: Thu, 21 Aug 2025 23:55:47 +0200 Subject: [PATCH 08/10] Raise MSRV --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- release-hook.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32d95107cc..18e0aa660c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ ### Changed - Upgrade to Rust edition 2024 -- Raise the minimum supported Rust version to `1.87` +- Raise the minimum supported Rust version to `1.88` - Don't follow symlinks in the file watcher - `dev new`: Don't add `.rustlings-state.txt` to `.gitignore` diff --git a/Cargo.toml b/Cargo.toml index 454f3b8018..764b6b6657 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ authors = [ repository = "https://github.com/rust-lang/rustlings" license = "MIT" edition = "2024" # On Update: Update the edition of `rustfmt` in `dev check` and `CARGO_TOML` in `dev new`. -rust-version = "1.87" +rust-version = "1.88" [workspace.dependencies] serde = { version = "1.0", features = ["derive"] } diff --git a/release-hook.sh b/release-hook.sh index 42135369ab..4934933793 100755 --- a/release-hook.sh +++ b/release-hook.sh @@ -13,4 +13,4 @@ cargo test --workspace cargo dev check --require-solutions # MSRV -cargo +1.87 dev check --require-solutions +cargo +1.88 dev check --require-solutions From 6ec2e194ae606ae4409a626ea0ac025229f6f4b1 Mon Sep 17 00:00:00 2001 From: mo8it Date: Fri, 22 Aug 2025 00:01:03 +0200 Subject: [PATCH 09/10] Apply Clippy lints --- src/embedded.rs | 8 ++++---- src/exercise.rs | 22 +++++++++++----------- src/list/state.rs | 17 ++++++++--------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/embedded.rs b/src/embedded.rs index 88c1fb0139..61a5f581e2 100644 --- a/src/embedded.rs +++ b/src/embedded.rs @@ -20,10 +20,10 @@ struct ExerciseFiles { } fn create_dir_if_not_exists(path: &str) -> Result<()> { - if let Err(e) = create_dir(path) { - if e.kind() != io::ErrorKind::AlreadyExists { - return Err(Error::from(e).context(format!("Failed to create the directory {path}"))); - } + if let Err(e) = create_dir(path) + && e.kind() != io::ErrorKind::AlreadyExists + { + return Err(Error::from(e).context(format!("Failed to create the directory {path}"))); } Ok(()) diff --git a/src/exercise.rs b/src/exercise.rs index 6f517bee7c..a0596b5b9e 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -48,17 +48,17 @@ fn run_bin( let success = cmd_runner.run_debug_bin(bin_name, output.as_deref_mut())?; - if let Some(output) = output { - if !success { - // This output is important to show the user that something went wrong. - // Otherwise, calling something like `exit(1)` in an exercise without further output - // leaves the user confused about why the exercise isn't done yet. - write_ansi(output, SetAttribute(Attribute::Bold)); - write_ansi(output, SetForegroundColor(Color::Red)); - output.extend_from_slice(b"The exercise didn't run successfully (nonzero exit code)"); - write_ansi(output, ResetColor); - output.push(b'\n'); - } + if let Some(output) = output + && !success + { + // This output is important to show the user that something went wrong. + // Otherwise, calling something like `exit(1)` in an exercise without further output + // leaves the user confused about why the exercise isn't done yet. + write_ansi(output, SetAttribute(Attribute::Bold)); + write_ansi(output, SetForegroundColor(Color::Red)); + output.extend_from_slice(b"The exercise didn't run successfully (nonzero exit code)"); + write_ansi(output, ResetColor); + output.push(b'\n'); } Ok(success) diff --git a/src/list/state.rs b/src/list/state.rs index 50d06be9cd..4fd1301d6b 100644 --- a/src/list/state.rs +++ b/src/list/state.rs @@ -118,8 +118,8 @@ impl<'a> ListState<'a> { } fn draw_exercise_name(&self, writer: &mut MaxLenWriter, exercise: &Exercise) -> io::Result<()> { - if !self.search_query.is_empty() { - if let Some((pre_highlight, highlight, post_highlight)) = exercise + if !self.search_query.is_empty() + && let Some((pre_highlight, highlight, post_highlight)) = exercise .name .find(&self.search_query) .and_then(|ind| exercise.name.split_at_checked(ind)) @@ -127,13 +127,12 @@ impl<'a> ListState<'a> { rest.split_at_checked(self.search_query.len()) .map(|x| (pre_highlight, x.0, x.1)) }) - { - writer.write_str(pre_highlight)?; - writer.stdout.queue(SetForegroundColor(Color::Magenta))?; - writer.write_str(highlight)?; - writer.stdout.queue(SetForegroundColor(Color::Reset))?; - return writer.write_str(post_highlight); - } + { + writer.write_str(pre_highlight)?; + writer.stdout.queue(SetForegroundColor(Color::Magenta))?; + writer.write_str(highlight)?; + writer.stdout.queue(SetForegroundColor(Color::Reset))?; + return writer.write_str(post_highlight); } writer.write_str(exercise.name) From 2af9e89ba536fad01aa828b06e0ac2174bad0f6d Mon Sep 17 00:00:00 2001 From: mo8it Date: Fri, 22 Aug 2025 00:05:12 +0200 Subject: [PATCH 10/10] chore: Release --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c743dd7fb7..f883653fd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -439,7 +439,7 @@ dependencies = [ [[package]] name = "rustlings" -version = "6.4.0" +version = "6.5.0" dependencies = [ "anyhow", "clap", @@ -455,7 +455,7 @@ dependencies = [ [[package]] name = "rustlings-macros" -version = "6.4.0" +version = "6.5.0" dependencies = [ "quote", "serde", diff --git a/Cargo.toml b/Cargo.toml index 764b6b6657..4469b28112 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ exclude = [ ] [workspace.package] -version = "6.4.0" +version = "6.5.0" authors = [ "Mo Bitar ", # https://github.com/mo8it "Liv ", # https://github.com/shadows-withal @@ -49,7 +49,7 @@ anyhow = "1.0" clap = { version = "4.5", features = ["derive"] } crossterm = { version = "0.29", default-features = false, features = ["windows", "events"] } notify = "8.0" -rustlings-macros = { path = "rustlings-macros", version = "=6.4.0" } +rustlings-macros = { path = "rustlings-macros", version = "=6.5.0" } serde_json = "1.0" serde.workspace = true toml.workspace = true