diff options
| -rw-r--r-- | .typos.toml | 3 | ||||
| -rw-r--r-- | Cargo.lock | 24 | ||||
| -rw-r--r-- | Cargo.toml | 6 | ||||
| -rw-r--r-- | dev/Cargo.toml | 14 | ||||
| -rw-r--r-- | exercises/03_if/if2.rs | 22 | ||||
| -rw-r--r-- | exercises/08_enums/enums2.rs | 2 | ||||
| -rw-r--r-- | exercises/10_modules/modules2.rs | 1 | ||||
| -rw-r--r-- | exercises/15_traits/traits3.rs | 2 | ||||
| -rw-r--r-- | exercises/19_smart_pointers/rc1.rs | 1 | ||||
| -rw-r--r-- | rustlings-macros/info.toml | 2 | ||||
| -rw-r--r-- | solutions/03_if/if2.rs | 26 | ||||
| -rw-r--r-- | solutions/08_enums/enums2.rs | 2 | ||||
| -rw-r--r-- | solutions/10_modules/modules2.rs | 1 | ||||
| -rw-r--r-- | solutions/15_traits/traits3.rs | 2 | ||||
| -rw-r--r-- | solutions/19_smart_pointers/rc1.rs | 1 | ||||
| -rw-r--r-- | src/dev/new.rs | 4 | ||||
| -rw-r--r-- | src/exercise.rs | 2 | ||||
| -rw-r--r-- | src/init.rs | 7 | ||||
| -rw-r--r-- | src/watch.rs | 26 | ||||
| -rw-r--r-- | src/watch/notify_event.rs | 4 | ||||
| -rw-r--r-- | src/watch/state.rs | 24 | ||||
| -rw-r--r-- | src/watch/terminal_event.rs | 63 |
22 files changed, 138 insertions, 101 deletions
diff --git a/.typos.toml b/.typos.toml index 2de6d58..743c874 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,3 +1,6 @@ +[default.extend-words] +"earch" = "earch" # Because of <s>earch in the list footer + [files] extend-exclude = [ "CHANGELOG.md", @@ -65,9 +65,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" [[package]] name = "autocfg" @@ -460,18 +460,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -530,18 +530,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -659,9 +659,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "utf8parse" @@ -19,7 +19,7 @@ edition = "2021" # On Update: Update the edition of the `rustfmt` command that c rust-version = "1.80" [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"] } [package] @@ -47,7 +47,7 @@ include = [ [dependencies] ahash = { version = "0.8.11", default-features = false } -anyhow = "1.0.86" +anyhow = "1.0.88" clap = { version = "4.5.17", features = ["derive"] } crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] } notify-debouncer-mini = { version = "0.4.1", default-features = false } @@ -58,7 +58,7 @@ serde.workspace = true toml_edit.workspace = true [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] tempfile = "3.12.0" diff --git a/dev/Cargo.toml b/dev/Cargo.toml index 2accf3a..29a557a 100644 --- a/dev/Cargo.toml +++ b/dev/Cargo.toml @@ -203,19 +203,21 @@ panic = "abort" panic = "abort" [lints.rust] -# You shouldn't write unsafe code in Rustlings +# You shouldn't write unsafe code in Rustlings! 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" +# Dead code warnings can't be avoided in some exercises and might distract while learning. +dead_code = "allow" [lints.clippy] -# You forgot a `todo!()` +# You forgot a `todo!()`! todo = "forbid" -# This can only happen by mistake in Rustlings +# This can only happen by mistake in Rustlings. empty_loop = "forbid" -# No infinite loops are needed in Rustlings +# No infinite loops are needed in Rustlings. infinite_loop = "deny" -# You shouldn't leak memory while still learning Rust +# You shouldn't leak memory while still learning Rust! mem_forget = "deny" # Currently, there are no disallowed methods. This line avoids problems when developing Rustlings. disallowed_methods = "allow" diff --git a/exercises/03_if/if2.rs b/exercises/03_if/if2.rs index 593a77a..10037f2 100644 --- a/exercises/03_if/if2.rs +++ b/exercises/03_if/if2.rs @@ -1,7 +1,7 @@ // TODO: Fix the compiler error on this function. -fn foo_if_fizz(fizzish: &str) -> &str { - if fizzish == "fizz" { - "foo" +fn picky_eater(food: &str) -> &str { + if food == "strawberry" { + "Yummy!" } else { 1 } @@ -18,18 +18,20 @@ mod tests { use super::*; #[test] - fn foo_for_fizz() { - // This means that calling `foo_if_fizz` with the argument "fizz" should return "foo". - assert_eq!(foo_if_fizz("fizz"), "foo"); + fn yummy_food() { + // This means that calling `picky_eater` with the argument "food" should return "Yummy!". + assert_eq!(picky_eater("strawberry"), "Yummy!"); } #[test] - fn bar_for_fuzz() { - assert_eq!(foo_if_fizz("fuzz"), "bar"); + fn neutral_food() { + assert_eq!(picky_eater("potato"), "I guess I can eat that."); } #[test] - fn default_to_baz() { - assert_eq!(foo_if_fizz("literally anything"), "baz"); + fn default_disliked_food() { + assert_eq!(picky_eater("broccoli"), "No thanks!"); + assert_eq!(picky_eater("gummy bears"), "No thanks!"); + assert_eq!(picky_eater("literally anything"), "No thanks!"); } } diff --git a/exercises/08_enums/enums2.rs b/exercises/08_enums/enums2.rs index 29ed1b6..d70f639 100644 --- a/exercises/08_enums/enums2.rs +++ b/exercises/08_enums/enums2.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - #[derive(Debug)] struct Point { x: u64, diff --git a/exercises/10_modules/modules2.rs b/exercises/10_modules/modules2.rs index 02eb80a..782a70e 100644 --- a/exercises/10_modules/modules2.rs +++ b/exercises/10_modules/modules2.rs @@ -1,7 +1,6 @@ // You can bring module paths into scopes and provide new names for them with // the `use` and `as` keywords. -#[allow(dead_code)] mod delicious_snacks { // TODO: Add the following two `use` statements after fixing them. // use self::fruits::PEAR as ???; diff --git a/exercises/15_traits/traits3.rs b/exercises/15_traits/traits3.rs index 2e8969e..c244650 100644 --- a/exercises/15_traits/traits3.rs +++ b/exercises/15_traits/traits3.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - trait Licensed { // TODO: Add a default implementation for `licensing_info` so that // implementors like the two structs below can share that default behavior diff --git a/exercises/19_smart_pointers/rc1.rs b/exercises/19_smart_pointers/rc1.rs index 48e19dc..ecd3438 100644 --- a/exercises/19_smart_pointers/rc1.rs +++ b/exercises/19_smart_pointers/rc1.rs @@ -8,7 +8,6 @@ use std::rc::Rc; #[derive(Debug)] struct Sun; -#[allow(dead_code)] #[derive(Debug)] enum Planet { Mercury(Rc<Sun>), diff --git a/rustlings-macros/info.toml b/rustlings-macros/info.toml index 0fe8343..2d42045 100644 --- a/rustlings-macros/info.toml +++ b/rustlings-macros/info.toml @@ -122,7 +122,7 @@ dir = "01_variables" test = false hint = """ 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` instead of `let`. diff --git a/solutions/03_if/if2.rs b/solutions/03_if/if2.rs index 440bba0..21c0dcd 100644 --- a/solutions/03_if/if2.rs +++ b/solutions/03_if/if2.rs @@ -1,10 +1,10 @@ -fn foo_if_fizz(fizzish: &str) -> &str { - if fizzish == "fizz" { - "foo" - } else if fizzish == "fuzz" { - "bar" +fn picky_eater(food: &str) -> &str { + if food == "strawberry" { + "Yummy!" + } else if food == "potato" { + "I guess I can eat that." } else { - "baz" + "No thanks!" } } @@ -17,17 +17,19 @@ mod tests { use super::*; #[test] - fn foo_for_fizz() { - assert_eq!(foo_if_fizz("fizz"), "foo"); + fn yummy_food() { + assert_eq!(picky_eater("strawberry"), "Yummy!"); } #[test] - fn bar_for_fuzz() { - assert_eq!(foo_if_fizz("fuzz"), "bar"); + fn neutral_food() { + assert_eq!(picky_eater("potato"), "I guess I can eat that."); } #[test] - fn default_to_baz() { - assert_eq!(foo_if_fizz("literally anything"), "baz"); + fn default_disliked_food() { + assert_eq!(picky_eater("broccoli"), "No thanks!"); + assert_eq!(picky_eater("gummy bears"), "No thanks!"); + assert_eq!(picky_eater("literally anything"), "No thanks!"); } } diff --git a/solutions/08_enums/enums2.rs b/solutions/08_enums/enums2.rs index 2ee0553..07aee26 100644 --- a/solutions/08_enums/enums2.rs +++ b/solutions/08_enums/enums2.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - #[derive(Debug)] struct Point { x: u64, diff --git a/solutions/10_modules/modules2.rs b/solutions/10_modules/modules2.rs index 298d76e..55c316d 100644 --- a/solutions/10_modules/modules2.rs +++ b/solutions/10_modules/modules2.rs @@ -1,4 +1,3 @@ -#[allow(dead_code)] mod delicious_snacks { // Added `pub` and used the expected alias after `as`. pub use self::fruits::PEAR as fruit; diff --git a/solutions/15_traits/traits3.rs b/solutions/15_traits/traits3.rs index 747d919..3d8ec85 100644 --- a/solutions/15_traits/traits3.rs +++ b/solutions/15_traits/traits3.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - trait Licensed { fn licensing_info(&self) -> String { "Default license".to_string() diff --git a/solutions/19_smart_pointers/rc1.rs b/solutions/19_smart_pointers/rc1.rs index 512eb9c..c0a41ab 100644 --- a/solutions/19_smart_pointers/rc1.rs +++ b/solutions/19_smart_pointers/rc1.rs @@ -8,7 +8,6 @@ use std::rc::Rc; #[derive(Debug)] struct Sun; -#[allow(dead_code)] #[derive(Debug)] enum Planet { Mercury(Rc<Sun>), diff --git a/src/dev/new.rs b/src/dev/new.rs index c765046..154cd22 100644 --- a/src/dev/new.rs +++ b/src/dev/new.rs @@ -6,7 +6,7 @@ use std::{ 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. 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("rust-analyzer.toml", &dir_path_str, RUST_ANALYZER_TOML)?; + create_rel_dir(".vscode", &dir_path_str)?; write_rel_file( ".vscode/extensions.json", diff --git a/src/exercise.rs b/src/exercise.rs index 7fb2343..8490828 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -131,7 +131,7 @@ pub trait RunnableExercise { 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() { clippy_cmd.args(["--profile", "test", "--", "-D", "warnings"]); } else { diff --git a/src/init.rs b/src/init.rs index 332bf52..ce49bb6 100644 --- a/src/init.rs +++ b/src/init.rs @@ -130,6 +130,9 @@ pub fn init() -> Result<()> { fs::write("Cargo.toml", updated_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) .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 target/ .vscode/ diff --git a/src/watch.rs b/src/watch.rs index a44b565..c937bfb 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Error, Result}; +use anyhow::{Error, Result}; use notify_debouncer_mini::{ new_debouncer, notify::{self, RecursiveMode}, @@ -7,7 +7,6 @@ use std::{ io::{self, Write}, path::Path, sync::mpsc::channel, - thread, time::Duration, }; @@ -16,11 +15,7 @@ use crate::{ list, }; -use self::{ - notify_event::NotifyEventHandler, - state::WatchState, - terminal_event::{terminal_event_handler, InputEvent}, -}; +use self::{notify_event::NotifyEventHandler, state::WatchState, terminal_event::InputEvent}; mod notify_event; mod state; @@ -47,7 +42,7 @@ fn run_watch( app_state: &mut AppState, notify_exercise_names: Option<&'static [&'static [u8]]>, ) -> Result<WatchExit> { - let (tx, rx) = channel(); + let (watch_event_sender, watch_event_receiver) = channel(); let mut manual_run = false; // Prevent dropping the guard until the end of the function. @@ -56,7 +51,7 @@ fn run_watch( let mut debouncer = new_debouncer( Duration::from_millis(200), NotifyEventHandler { - tx: tx.clone(), + sender: watch_event_sender.clone(), exercise_names, }, ) @@ -72,16 +67,12 @@ fn run_watch( 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(); - watch_state.run_current_exercise(&mut stdout)?; - thread::Builder::new() - .spawn(move || terminal_event_handler(tx, manual_run)) - .context("Failed to spawn a thread to handle terminal events")?; + watch_state.run_current_exercise(&mut stdout)?; - while let Ok(event) = rx.recv() { + while let Ok(event) = watch_event_receiver.recv() { match event { WatchEvent::Input(InputEvent::Next) => match watch_state.next_exercise(&mut stdout)? { ExercisesProgress::AllDone => break, @@ -154,8 +145,9 @@ pub fn watch( } const QUIT_MSG: &[u8] = b" + 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 = " diff --git a/src/watch/notify_event.rs b/src/watch/notify_event.rs index 7471640..9b23525 100644 --- a/src/watch/notify_event.rs +++ b/src/watch/notify_event.rs @@ -4,7 +4,7 @@ use std::sync::mpsc::Sender; use super::WatchEvent; pub struct NotifyEventHandler { - pub tx: Sender<WatchEvent>, + pub sender: Sender<WatchEvent>, /// Used to report which exercise was modified. 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. // After dropping the receiver, the debouncer guard should also be dropped. - let _ = self.tx.send(output_event); + let _ = self.sender.send(output_event); } } diff --git a/src/watch/state.rs b/src/watch/state.rs index e66cbee..6e76001 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -5,7 +5,11 @@ use crossterm::{ }, terminal, QueueableCommand, }; -use std::io::{self, StdoutLock, Write}; +use std::{ + io::{self, StdoutLock, Write}, + sync::mpsc::Sender, + thread, +}; use crate::{ app_state::{AppState, ExercisesProgress}, @@ -14,6 +18,11 @@ use crate::{ term::progress_bar, }; +use super::{ + terminal_event::{terminal_event_handler, InputPauseGuard}, + WatchEvent, +}; + #[derive(PartialEq, Eq)] enum DoneStatus { DoneWithSolution(String), @@ -31,11 +40,19 @@ pub struct 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() .context("Failed to get the terminal size")? .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 { app_state, 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<()> { + // Ignore any input until running the exercise is done. + let _input_pause_guard = InputPauseGuard::scoped_pause(); + self.show_hint = false; writeln!( diff --git a/src/watch/terminal_event.rs b/src/watch/terminal_event.rs index ca3a846..2a1dfdc 100644 --- a/src/watch/terminal_event.rs +++ b/src/watch/terminal_event.rs @@ -1,8 +1,32 @@ use crossterm::event::{self, Event, KeyCode, KeyEventKind}; -use std::sync::mpsc::Sender; +use std::sync::{ + atomic::{AtomicBool, Ordering::Relaxed}, + mpsc::Sender, +}; 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 { Run, Next, @@ -11,46 +35,41 @@ pub enum InputEvent { Quit, } -pub fn terminal_event_handler(tx: Sender<WatchEvent>, manual_run: bool) { - let last_input_event = loop { - let terminal_event = match event::read() { - Ok(v) => v, - 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) => { +pub fn terminal_event_handler(sender: Sender<WatchEvent>, manual_run: bool) { + let last_watch_event = loop { + match event::read() { + Ok(Event::Key(key)) => { match key.kind { KeyEventKind::Release | KeyEventKind::Repeat => continue, KeyEventKind::Press => (), } + if INPUT_PAUSED.load(Relaxed) { + continue; + } + let input_event = match key.code { KeyCode::Char('n') => InputEvent::Next, KeyCode::Char('h') => InputEvent::Hint, - KeyCode::Char('l') => break InputEvent::List, - KeyCode::Char('q') => break InputEvent::Quit, + KeyCode::Char('l') => break WatchEvent::Input(InputEvent::List), + KeyCode::Char('q') => break WatchEvent::Input(InputEvent::Quit), KeyCode::Char('r') if manual_run => InputEvent::Run, _ => continue, }; - if tx.send(WatchEvent::Input(input_event)).is_err() { + if sender.send(WatchEvent::Input(input_event)).is_err() { return; } } - Event::Resize(width, _) => { - if tx.send(WatchEvent::TerminalResize { width }).is_err() { + Ok(Event::Resize(width, _)) => { + if sender.send(WatchEvent::TerminalResize { width }).is_err() { 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); } |
