Compare commits

...

13 commits

Author SHA1 Message Date
Eric Eastwood e525e52a7a
Merge fa6680ff76 into e6cb104294 2024-11-11 13:59:09 -06:00
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
Eric Eastwood fa6680ff76 Correctly point to sections 2024-10-16 11:09:09 -05:00
Eric Eastwood 2bccdcbd2c Use simple caution message with expand for more details 2024-10-16 11:07:42 -05:00
Eric Eastwood 60e0d4ae8a Fix some grammar 2024-10-16 11:01:41 -05:00
Eric Eastwood a025ce0538 Add warning about rust-analyzer not working if you clone and use the repo directly
> Yes, you are right, if you just clone the repository and try to edit the exercises, the language server will not work. This is one downside of the current approach. But this only affects developing exercises.
>
> The new method of doing Rustlings is to install Rustlings using `cargo install rustlings` (not published yet), then running `rustlings init`. No repo cloning happens. Instead, the directory `rustlings/` will be created where you find the exercises. The language server works there out of the box :)
>
> I need to add a warning when people try to work on the exercises from the repository. Thanks pointing this out.
>
> -- @mo8it, https://github.com/rust-lang/rustlings/issues/1935#issuecomment-2067664066

Other references:

 - Previous `rustlings lsp` command: https://github.com/rust-lang/rustlings/pull/1026
 - The changelog says "LSP support out of the box", https://github.com/rust-lang/rustlings/blob/main/CHANGELOG.md#lsp-support-out-of-the-box
2024-10-14 14:11:09 -05:00
7 changed files with 111 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

@ -45,6 +45,18 @@ cargo install rustlings
</details> </details>
> [!CAUTION]
> Don't try to clone the repository to do the exercises! `rust-analyzer` won't work in that case. Please follow the instructions above instead.
>
> <details>
> <summary>Why?</summary>
>
>The intended way to run Rustlings is to install the binary and run `rustlings init` as described in the installation/initialization sections. This generates a `Cargo.toml` (different than what you see in the repository) that includes each exercise as a separate binary target which is enough for `rust-analyzer` to work.
>
>If you just clone the repository and try to run and edit the exercises directly, the language server will not work.
>
> </details>
### Initialization ### Initialization
After installing Rustlings, run the following command to initialize the `rustlings/` directory: After installing Rustlings, run the following command to initialize the `rustlings/` directory:

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" ")?;
} }
@ -167,11 +177,12 @@ impl<'a> ListState<'a> {
writer.write_ascii(b"DONE ")?; writer.write_ascii(b"DONE ")?;
} else { } else {
writer.stdout.queue(SetForegroundColor(Color::Yellow))?; writer.stdout.queue(SetForegroundColor(Color::Yellow))?;
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 {