mirror of
https://github.com/rust-lang/rustlings.git
synced 2024-12-25 23:10:30 +00:00
Compare commits
10 commits
5cafa60553
...
d16ce83be2
Author | SHA1 | Date | |
---|---|---|---|
|
d16ce83be2 | ||
|
eff2ce8a23 | ||
|
fd33c29b26 | ||
|
f49164e69b | ||
|
9bc7bbe4b4 | ||
|
46ad25f925 | ||
|
fa6680ff76 | ||
|
2bccdcbd2c | ||
|
60e0d4ae8a | ||
|
a025ce0538 |
6 changed files with 73 additions and 40 deletions
36
Cargo.lock
generated
36
Cargo.lock
generated
|
@ -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",
|
||||||
|
@ -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",
|
||||||
|
|
|
@ -46,7 +46,7 @@ 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"
|
||||||
|
@ -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"
|
||||||
|
|
12
README.md
12
README.md
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue