Compare commits

...

24 commits

Author SHA1 Message Date
Kacper Poneta 57341c4156
Merge 59e8f70e55 into 64b2f18d92 2024-09-17 10:47:02 +08:00
Mo 64b2f18d92
Merge pull request #2103 from senekor/senk/kvuzvzqqkskk
Remove redundant enum definition task
2024-09-16 12:56:28 +02:00
Mo 2894f3c45c
Merge pull request #2110 from senekor/remo/skkynvtqxkoz
Make if2 less confusing
2024-09-16 12:54:20 +02:00
Mo 1bae2dcb00
Merge pull request #2109 from bri-rose/main
grammatical error in info.toml
2024-09-14 23:52:54 +02:00
Remo Senekowitsch b540c6df25 Make if2 less confusing
Some people would get stuck on this exercise, trying to understand the meaning
behind foo, fuzz, baz etc. Making the theme of the code make a little more sense
to humans should hopefully prevent people from getting confused by abstract and
non-sensical tests.
2024-09-14 10:03:52 +02:00
bri-rose 8b476e678a
Update info.toml
Fixed grammatical error, subject/verb agreement at line 124-125.
2024-09-13 10:23:05 -05:00
mo8it 47f8a0cbe5 Add rust-analyzer.toml on dev new 2024-09-13 16:39:28 +02:00
mo8it 9459eef032 Use Clippy with Rust-Analyzer 2024-09-13 16:38:53 +02:00
mo8it 5aaa8924a6 <s>earch isn't a typo 2024-09-13 15:07:53 +02:00
mo8it 4ffce1c297 Move lint to Rust lints 2024-09-13 14:59:34 +02:00
mo8it 0513660b05 Allow dead code for all exercises and solutions 2024-09-13 14:56:46 +02:00
mo8it 3947c4de28 Pause input while running an exercise 2024-09-12 17:46:06 +02:00
mo8it 664228ef8b Improve quit message 2024-09-12 17:46:06 +02:00
mo8it 234a61a3ee Update deps 2024-09-12 17:46:06 +02:00
mo8it 83d1275d72 Add missing # in comment 2024-09-12 17:46:06 +02:00
Mo 45abd7d59e
Merge pull request #2107 from alibektas/ratoml_for_rustlings
Add rust-analyzer.toml file
2024-09-12 15:49:31 +02:00
Ali Bektas 88e10a9e54 hardcode ratoml in init.rs 2024-09-12 15:46:09 +02:00
Ali Bektas 1f624d4c2a Add rust-analyzer.toml file 2024-09-12 15:26:40 +02:00
Remo Senekowitsch 9a25309c1c Remove redundant enum definition task
The exercise enums2.rs already contains a task where an identical enum
has to be defined.
2024-09-11 16:57:12 +02:00
mo8it 59e8f70e55 Format code 2024-07-12 18:31:23 +02:00
mo8it 4c8365fe88 Update dev/Cargo.toml 2024-07-12 18:25:01 +02:00
Kacper Poneta 52af0674c1 changed the task to make it more appropriate 2024-07-12 18:14:40 +02:00
Kacper Poneta 938b90e5f2 very small solution update 2024-07-11 22:55:48 +02:00
Kacper Poneta 55cc8584bd added exercise 2024-07-11 22:53:38 +02:00
25 changed files with 263 additions and 102 deletions

View file

@ -1,3 +1,6 @@
[default.extend-words]
"earch" = "earch" # Because of <s>earch in the list footer
[files] [files]
extend-exclude = [ extend-exclude = [
"CHANGELOG.md", "CHANGELOG.md",

24
Cargo.lock generated
View file

@ -65,9 +65,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.86" version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
@ -460,18 +460,18 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.3" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
] ]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.35" version = "0.38.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"errno", "errno",
@ -530,18 +530,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.209" version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.209" version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -659,9 +659,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.12" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"

View file

@ -19,7 +19,7 @@ edition = "2021" # On Update: Update the edition of the `rustfmt` command that c
rust-version = "1.80" rust-version = "1.80"
[workspace.dependencies] [workspace.dependencies]
serde = { version = "1.0.209", features = ["derive"] } serde = { version = "1.0.210", features = ["derive"] }
toml_edit = { version = "0.22.20", default-features = false, features = ["parse", "serde"] } toml_edit = { version = "0.22.20", default-features = false, features = ["parse", "serde"] }
[package] [package]
@ -47,7 +47,7 @@ include = [
[dependencies] [dependencies]
ahash = { version = "0.8.11", default-features = false } ahash = { version = "0.8.11", default-features = false }
anyhow = "1.0.86" anyhow = "1.0.88"
clap = { version = "4.5.17", features = ["derive"] } clap = { version = "4.5.17", 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-debouncer-mini = { version = "0.4.1", default-features = false } notify-debouncer-mini = { version = "0.4.1", default-features = false }
@ -58,7 +58,7 @@ serde.workspace = true
toml_edit.workspace = true toml_edit.workspace = true
[target.'cfg(not(windows))'.dependencies] [target.'cfg(not(windows))'.dependencies]
rustix = { version = "0.38.35", default-features = false, features = ["std", "stdio", "termios"] } rustix = { version = "0.38.37", default-features = false, features = ["std", "stdio", "termios"] }
[dev-dependencies] [dev-dependencies]
tempfile = "3.12.0" tempfile = "3.12.0"

View file

@ -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" },
@ -203,19 +205,21 @@ panic = "abort"
panic = "abort" panic = "abort"
[lints.rust] [lints.rust]
# You shouldn't write unsafe code in Rustlings # You shouldn't write unsafe code in Rustlings!
unsafe_code = "forbid" unsafe_code = "forbid"
# You don't need unstable features in Rustlings and shouldn't rely on them while learning Rust # You don't need unstable features in Rustlings and shouldn't rely on them while learning Rust.
unstable_features = "forbid" unstable_features = "forbid"
# Dead code warnings can't be avoided in some exercises and might distract while learning.
dead_code = "allow"
[lints.clippy] [lints.clippy]
# You forgot a `todo!()` # You forgot a `todo!()`!
todo = "forbid" todo = "forbid"
# This can only happen by mistake in Rustlings # This can only happen by mistake in Rustlings.
empty_loop = "forbid" empty_loop = "forbid"
# No infinite loops are needed in Rustlings # No infinite loops are needed in Rustlings.
infinite_loop = "deny" infinite_loop = "deny"
# You shouldn't leak memory while still learning Rust # You shouldn't leak memory while still learning Rust!
mem_forget = "deny" mem_forget = "deny"
# Currently, there are no disallowed methods. This line avoids problems when developing Rustlings. # Currently, there are no disallowed methods. This line avoids problems when developing Rustlings.
disallowed_methods = "allow" disallowed_methods = "allow"

View file

@ -1,7 +1,7 @@
// TODO: Fix the compiler error on this function. // TODO: Fix the compiler error on this function.
fn foo_if_fizz(fizzish: &str) -> &str { fn picky_eater(food: &str) -> &str {
if fizzish == "fizz" { if food == "strawberry" {
"foo" "Yummy!"
} else { } else {
1 1
} }
@ -18,18 +18,20 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn foo_for_fizz() { fn yummy_food() {
// This means that calling `foo_if_fizz` with the argument "fizz" should return "foo". // This means that calling `picky_eater` with the argument "food" should return "Yummy!".
assert_eq!(foo_if_fizz("fizz"), "foo"); assert_eq!(picky_eater("strawberry"), "Yummy!");
} }
#[test] #[test]
fn bar_for_fuzz() { fn neutral_food() {
assert_eq!(foo_if_fizz("fuzz"), "bar"); assert_eq!(picky_eater("potato"), "I guess I can eat that.");
} }
#[test] #[test]
fn default_to_baz() { fn default_disliked_food() {
assert_eq!(foo_if_fizz("literally anything"), "baz"); assert_eq!(picky_eater("broccoli"), "No thanks!");
assert_eq!(picky_eater("gummy bears"), "No thanks!");
assert_eq!(picky_eater("literally anything"), "No thanks!");
} }
} }

View file

@ -1,5 +1,3 @@
#![allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
struct Point { struct Point {
x: u64, x: u64,

View file

@ -4,7 +4,11 @@ struct Point {
} }
enum Message { enum Message {
// TODO: Implement the message variant types based on their usage below. Resize { width: u64, height: u64 },
Move(Point),
Echo(String),
ChangeColor(u8, u8, u8),
Quit,
} }
struct State { struct State {

View file

@ -1,7 +1,6 @@
// You can bring module paths into scopes and provide new names for them with // You can bring module paths into scopes and provide new names for them with
// the `use` and `as` keywords. // the `use` and `as` keywords.
#[allow(dead_code)]
mod delicious_snacks { mod delicious_snacks {
// TODO: Add the following two `use` statements after fixing them. // TODO: Add the following two `use` statements after fixing them.
// use self::fruits::PEAR as ???; // use self::fruits::PEAR as ???;

View 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);
}
}

View file

@ -1,5 +1,3 @@
#![allow(dead_code)]
trait Licensed { trait Licensed {
// TODO: Add a default implementation for `licensing_info` so that // TODO: Add a default implementation for `licensing_info` so that
// implementors like the two structs below can share that default behavior // implementors like the two structs below can share that default behavior

View file

@ -8,7 +8,6 @@ use std::rc::Rc;
#[derive(Debug)] #[derive(Debug)]
struct Sun; struct Sun;
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
enum Planet { enum Planet {
Mercury(Rc<Sun>), Mercury(Rc<Sun>),

View file

@ -122,7 +122,7 @@ dir = "01_variables"
test = false test = false
hint = """ hint = """
We know about variables and mutability, but there is another important type of We know about variables and mutability, but there is another important type of
variables available: constants. variable available: constants.
Constants are always immutable. They are declared with the keyword `const` Constants are always immutable. They are declared with the keyword `const`
instead of `let`. instead of `let`.
@ -749,6 +749,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]]

View file

@ -1,10 +1,10 @@
fn foo_if_fizz(fizzish: &str) -> &str { fn picky_eater(food: &str) -> &str {
if fizzish == "fizz" { if food == "strawberry" {
"foo" "Yummy!"
} else if fizzish == "fuzz" { } else if food == "potato" {
"bar" "I guess I can eat that."
} else { } else {
"baz" "No thanks!"
} }
} }
@ -17,17 +17,19 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn foo_for_fizz() { fn yummy_food() {
assert_eq!(foo_if_fizz("fizz"), "foo"); assert_eq!(picky_eater("strawberry"), "Yummy!");
} }
#[test] #[test]
fn bar_for_fuzz() { fn neutral_food() {
assert_eq!(foo_if_fizz("fuzz"), "bar"); assert_eq!(picky_eater("potato"), "I guess I can eat that.");
} }
#[test] #[test]
fn default_to_baz() { fn default_disliked_food() {
assert_eq!(foo_if_fizz("literally anything"), "baz"); assert_eq!(picky_eater("broccoli"), "No thanks!");
assert_eq!(picky_eater("gummy bears"), "No thanks!");
assert_eq!(picky_eater("literally anything"), "No thanks!");
} }
} }

View file

@ -1,5 +1,3 @@
#![allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
struct Point { struct Point {
x: u64, x: u64,

View file

@ -1,4 +1,3 @@
#[allow(dead_code)]
mod delicious_snacks { mod delicious_snacks {
// Added `pub` and used the expected alias after `as`. // Added `pub` and used the expected alias after `as`.
pub use self::fruits::PEAR as fruit; pub use self::fruits::PEAR as fruit;

View 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);
}
}

View file

@ -1,5 +1,3 @@
#![allow(dead_code)]
trait Licensed { trait Licensed {
fn licensing_info(&self) -> String { fn licensing_info(&self) -> String {
"Default license".to_string() "Default license".to_string()

View file

@ -8,7 +8,6 @@ use std::rc::Rc;
#[derive(Debug)] #[derive(Debug)]
struct Sun; struct Sun;
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
enum Planet { enum Planet {
Mercury(Rc<Sun>), Mercury(Rc<Sun>),

View file

@ -6,7 +6,7 @@ use std::{
process::Command, process::Command,
}; };
use crate::CURRENT_FORMAT_VERSION; use crate::{init::RUST_ANALYZER_TOML, CURRENT_FORMAT_VERSION};
// Create a directory relative to the current directory and print its path. // Create a directory relative to the current directory and print its path.
fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> { fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> {
@ -62,6 +62,8 @@ pub fn new(path: &Path, no_git: bool) -> Result<()> {
write_rel_file("README.md", &dir_path_str, README)?; write_rel_file("README.md", &dir_path_str, README)?;
write_rel_file("rust-analyzer.toml", &dir_path_str, RUST_ANALYZER_TOML)?;
create_rel_dir(".vscode", &dir_path_str)?; create_rel_dir(".vscode", &dir_path_str)?;
write_rel_file( write_rel_file(
".vscode/extensions.json", ".vscode/extensions.json",

View file

@ -131,7 +131,7 @@ pub trait RunnableExercise {
let mut clippy_cmd = cmd_runner.cargo("clippy", bin_name, output.as_deref_mut()); let mut clippy_cmd = cmd_runner.cargo("clippy", bin_name, output.as_deref_mut());
// `--profile test` is required to also check code with `[cfg(test)]`. // `--profile test` is required to also check code with `#[cfg(test)]`.
if FORCE_STRICT_CLIPPY || self.strict_clippy() { if FORCE_STRICT_CLIPPY || self.strict_clippy() {
clippy_cmd.args(["--profile", "test", "--", "-D", "warnings"]); clippy_cmd.args(["--profile", "test", "--", "-D", "warnings"]);
} else { } else {

View file

@ -130,6 +130,9 @@ pub fn init() -> Result<()> {
fs::write("Cargo.toml", updated_cargo_toml) fs::write("Cargo.toml", updated_cargo_toml)
.context("Failed to create the file `rustlings/Cargo.toml`")?; .context("Failed to create the file `rustlings/Cargo.toml`")?;
fs::write("rust-analyzer.toml", RUST_ANALYZER_TOML)
.context("Failed to create the file `rustlings/rust-analyzer.toml`")?;
fs::write(".gitignore", GITIGNORE) fs::write(".gitignore", GITIGNORE)
.context("Failed to create the file `rustlings/.gitignore`")?; .context("Failed to create the file `rustlings/.gitignore`")?;
@ -169,6 +172,10 @@ const INIT_SOLUTION_FILE: &[u8] = b"fn main() {
} }
"; ";
pub const RUST_ANALYZER_TOML: &[u8] = br#"check.command = "clippy"
check.extraArgs = ["--profile", "test"]
"#;
const GITIGNORE: &[u8] = b"Cargo.lock const GITIGNORE: &[u8] = b"Cargo.lock
target/ target/
.vscode/ .vscode/

View file

@ -1,4 +1,4 @@
use anyhow::{Context, Error, Result}; use anyhow::{Error, Result};
use notify_debouncer_mini::{ use notify_debouncer_mini::{
new_debouncer, new_debouncer,
notify::{self, RecursiveMode}, notify::{self, RecursiveMode},
@ -7,7 +7,6 @@ use std::{
io::{self, Write}, io::{self, Write},
path::Path, path::Path,
sync::mpsc::channel, sync::mpsc::channel,
thread,
time::Duration, time::Duration,
}; };
@ -16,11 +15,7 @@ use crate::{
list, list,
}; };
use self::{ use self::{notify_event::NotifyEventHandler, state::WatchState, terminal_event::InputEvent};
notify_event::NotifyEventHandler,
state::WatchState,
terminal_event::{terminal_event_handler, InputEvent},
};
mod notify_event; mod notify_event;
mod state; mod state;
@ -47,7 +42,7 @@ fn run_watch(
app_state: &mut AppState, app_state: &mut AppState,
notify_exercise_names: Option<&'static [&'static [u8]]>, notify_exercise_names: Option<&'static [&'static [u8]]>,
) -> Result<WatchExit> { ) -> Result<WatchExit> {
let (tx, rx) = channel(); let (watch_event_sender, watch_event_receiver) = channel();
let mut manual_run = false; let mut manual_run = false;
// Prevent dropping the guard until the end of the function. // Prevent dropping the guard until the end of the function.
@ -56,7 +51,7 @@ fn run_watch(
let mut debouncer = new_debouncer( let mut debouncer = new_debouncer(
Duration::from_millis(200), Duration::from_millis(200),
NotifyEventHandler { NotifyEventHandler {
tx: tx.clone(), sender: watch_event_sender.clone(),
exercise_names, exercise_names,
}, },
) )
@ -72,16 +67,12 @@ fn run_watch(
None None
}; };
let mut watch_state = WatchState::build(app_state, manual_run)?; let mut watch_state = WatchState::build(app_state, watch_event_sender, manual_run)?;
let mut stdout = io::stdout().lock(); let mut stdout = io::stdout().lock();
watch_state.run_current_exercise(&mut stdout)?; watch_state.run_current_exercise(&mut stdout)?;
thread::Builder::new() while let Ok(event) = watch_event_receiver.recv() {
.spawn(move || terminal_event_handler(tx, manual_run))
.context("Failed to spawn a thread to handle terminal events")?;
while let Ok(event) = rx.recv() {
match event { match event {
WatchEvent::Input(InputEvent::Next) => match watch_state.next_exercise(&mut stdout)? { WatchEvent::Input(InputEvent::Next) => match watch_state.next_exercise(&mut stdout)? {
ExercisesProgress::AllDone => break, ExercisesProgress::AllDone => break,
@ -154,8 +145,9 @@ pub fn watch(
} }
const QUIT_MSG: &[u8] = b" const QUIT_MSG: &[u8] = b"
We hope you're enjoying learning Rust! We hope you're enjoying learning Rust!
If you want to continue working on the exercises at a later point, you can simply run `rustlings` again. If you want to continue working on the exercises at a later point, you can simply run `rustlings` again in this directory.
"; ";
const NOTIFY_ERR: &str = " const NOTIFY_ERR: &str = "

View file

@ -4,7 +4,7 @@ use std::sync::mpsc::Sender;
use super::WatchEvent; use super::WatchEvent;
pub struct NotifyEventHandler { pub struct NotifyEventHandler {
pub tx: Sender<WatchEvent>, pub sender: Sender<WatchEvent>,
/// Used to report which exercise was modified. /// Used to report which exercise was modified.
pub exercise_names: &'static [&'static [u8]], pub exercise_names: &'static [&'static [u8]],
} }
@ -47,6 +47,6 @@ impl notify_debouncer_mini::DebounceEventHandler for NotifyEventHandler {
// An error occurs when the receiver is dropped. // An error occurs when the receiver is dropped.
// After dropping the receiver, the debouncer guard should also be dropped. // After dropping the receiver, the debouncer guard should also be dropped.
let _ = self.tx.send(output_event); let _ = self.sender.send(output_event);
} }
} }

View file

@ -5,7 +5,11 @@ use crossterm::{
}, },
terminal, QueueableCommand, terminal, QueueableCommand,
}; };
use std::io::{self, StdoutLock, Write}; use std::{
io::{self, StdoutLock, Write},
sync::mpsc::Sender,
thread,
};
use crate::{ use crate::{
app_state::{AppState, ExercisesProgress}, app_state::{AppState, ExercisesProgress},
@ -14,6 +18,11 @@ use crate::{
term::progress_bar, term::progress_bar,
}; };
use super::{
terminal_event::{terminal_event_handler, InputPauseGuard},
WatchEvent,
};
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
enum DoneStatus { enum DoneStatus {
DoneWithSolution(String), DoneWithSolution(String),
@ -31,11 +40,19 @@ pub struct WatchState<'a> {
} }
impl<'a> WatchState<'a> { impl<'a> WatchState<'a> {
pub fn build(app_state: &'a mut AppState, manual_run: bool) -> Result<Self> { pub fn build(
app_state: &'a mut AppState,
watch_event_sender: Sender<WatchEvent>,
manual_run: bool,
) -> Result<Self> {
let term_width = terminal::size() let term_width = terminal::size()
.context("Failed to get the terminal size")? .context("Failed to get the terminal size")?
.0; .0;
thread::Builder::new()
.spawn(move || terminal_event_handler(watch_event_sender, manual_run))
.context("Failed to spawn a thread to handle terminal events")?;
Ok(Self { Ok(Self {
app_state, app_state,
output: Vec::with_capacity(OUTPUT_CAPACITY), output: Vec::with_capacity(OUTPUT_CAPACITY),
@ -47,6 +64,9 @@ impl<'a> WatchState<'a> {
} }
pub fn run_current_exercise(&mut self, stdout: &mut StdoutLock) -> Result<()> { pub fn run_current_exercise(&mut self, stdout: &mut StdoutLock) -> Result<()> {
// Ignore any input until running the exercise is done.
let _input_pause_guard = InputPauseGuard::scoped_pause();
self.show_hint = false; self.show_hint = false;
writeln!( writeln!(

View file

@ -1,8 +1,32 @@
use crossterm::event::{self, Event, KeyCode, KeyEventKind}; use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use std::sync::mpsc::Sender; use std::sync::{
atomic::{AtomicBool, Ordering::Relaxed},
mpsc::Sender,
};
use super::WatchEvent; use super::WatchEvent;
static INPUT_PAUSED: AtomicBool = AtomicBool::new(false);
// Private unit type to force using the constructor function.
#[must_use = "When the guard is dropped, the input is unpaused"]
pub struct InputPauseGuard(());
impl InputPauseGuard {
#[inline]
pub fn scoped_pause() -> Self {
INPUT_PAUSED.store(true, Relaxed);
Self(())
}
}
impl Drop for InputPauseGuard {
#[inline]
fn drop(&mut self) {
INPUT_PAUSED.store(false, Relaxed);
}
}
pub enum InputEvent { pub enum InputEvent {
Run, Run,
Next, Next,
@ -11,46 +35,41 @@ pub enum InputEvent {
Quit, Quit,
} }
pub fn terminal_event_handler(tx: Sender<WatchEvent>, manual_run: bool) { pub fn terminal_event_handler(sender: Sender<WatchEvent>, manual_run: bool) {
let last_input_event = loop { let last_watch_event = loop {
let terminal_event = match event::read() { match event::read() {
Ok(v) => v, Ok(Event::Key(key)) => {
Err(e) => {
// If `send` returns an error, then the receiver is dropped and
// a shutdown has been already initialized.
let _ = tx.send(WatchEvent::TerminalEventErr(e));
return;
}
};
match terminal_event {
Event::Key(key) => {
match key.kind { match key.kind {
KeyEventKind::Release | KeyEventKind::Repeat => continue, KeyEventKind::Release | KeyEventKind::Repeat => continue,
KeyEventKind::Press => (), KeyEventKind::Press => (),
} }
if INPUT_PAUSED.load(Relaxed) {
continue;
}
let input_event = match key.code { let input_event = match key.code {
KeyCode::Char('n') => InputEvent::Next, KeyCode::Char('n') => InputEvent::Next,
KeyCode::Char('h') => InputEvent::Hint, KeyCode::Char('h') => InputEvent::Hint,
KeyCode::Char('l') => break InputEvent::List, KeyCode::Char('l') => break WatchEvent::Input(InputEvent::List),
KeyCode::Char('q') => break InputEvent::Quit, KeyCode::Char('q') => break WatchEvent::Input(InputEvent::Quit),
KeyCode::Char('r') if manual_run => InputEvent::Run, KeyCode::Char('r') if manual_run => InputEvent::Run,
_ => continue, _ => continue,
}; };
if tx.send(WatchEvent::Input(input_event)).is_err() { if sender.send(WatchEvent::Input(input_event)).is_err() {
return; return;
} }
} }
Event::Resize(width, _) => { Ok(Event::Resize(width, _)) => {
if tx.send(WatchEvent::TerminalResize { width }).is_err() { if sender.send(WatchEvent::TerminalResize { width }).is_err() {
return; return;
} }
} }
Event::FocusGained | Event::FocusLost | Event::Mouse(_) => continue, Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => continue,
Err(e) => break WatchEvent::TerminalEventErr(e),
} }
}; };
let _ = tx.send(WatchEvent::Input(last_input_event)); let _ = sender.send(last_watch_event);
} }