summaryrefslogtreecommitdiff
path: root/src/watch.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/watch.rs')
-rw-r--r--src/watch.rs181
1 files changed, 12 insertions, 169 deletions
diff --git a/src/watch.rs b/src/watch.rs
index 1503fdf..967f98c 100644
--- a/src/watch.rs
+++ b/src/watch.rs
@@ -1,25 +1,18 @@
use anyhow::Result;
-use crossterm::{
- style::{Attribute, ContentStyle, Stylize},
- terminal::{Clear, ClearType},
- ExecutableCommand,
-};
-use notify_debouncer_mini::{
- new_debouncer, notify::RecursiveMode, DebounceEventResult, DebouncedEventKind,
-};
+use notify_debouncer_mini::{new_debouncer, notify::RecursiveMode};
use std::{
- fmt::Write as _,
- io::{self, BufRead, StdoutLock, Write},
+ io::{self, BufRead, Write},
path::Path,
- sync::mpsc::{channel, sync_channel, Receiver},
+ sync::mpsc::{channel, sync_channel},
thread,
time::Duration,
};
-use crate::{
- exercise::{self, Exercise},
- state_file::StateFile,
-};
+mod state;
+
+use crate::{exercise::Exercise, state_file::StateFile};
+
+use self::state::WatchState;
enum Event {
Hint,
@@ -27,130 +20,6 @@ enum Event {
Quit,
}
-struct WatchState<'a> {
- writer: StdoutLock<'a>,
- rx: Receiver<DebounceEventResult>,
- exercises: &'a [Exercise],
- exercise: &'a Exercise,
- current_exercise_ind: usize,
- stdout: Option<Vec<u8>>,
- stderr: Option<Vec<u8>>,
- message: Option<String>,
- prompt: Vec<u8>,
-}
-
-impl<'a> WatchState<'a> {
- fn run_exercise(&mut self) -> Result<bool> {
- let output = self.exercise.run()?;
-
- if !output.status.success() {
- self.stdout = Some(output.stdout);
- self.stderr = Some(output.stderr);
- return Ok(false);
- }
-
- if let exercise::State::Pending(context) = self.exercise.state()? {
- let mut message = format!(
- "
-You can keep working on this exercise or jump into the next one by removing the {} comment:
-
-",
- "`I AM NOT DONE`".bold(),
- );
-
- for context_line in context {
- let formatted_line = if context_line.important {
- context_line.line.bold()
- } else {
- context_line.line.stylize()
- };
-
- writeln!(
- message,
- "{:>2} {} {}",
- ContentStyle {
- foreground_color: Some(crossterm::style::Color::Blue),
- background_color: None,
- underline_color: None,
- attributes: Attribute::Bold.into()
- }
- .apply(context_line.number),
- "|".blue(),
- formatted_line,
- )?;
- }
-
- self.stdout = Some(output.stdout);
- self.message = Some(message);
- return Ok(false);
- }
-
- Ok(true)
- }
-
- fn try_recv_event(&mut self) -> Result<()> {
- let Ok(events) = self.rx.recv_timeout(Duration::from_millis(100)) else {
- return Ok(());
- };
-
- if let Some(current_exercise_ind) = events?
- .iter()
- .filter_map(|event| {
- if event.kind != DebouncedEventKind::Any
- || !event.path.extension().is_some_and(|ext| ext == "rs")
- {
- return None;
- }
-
- self.exercises
- .iter()
- .position(|exercise| event.path.ends_with(&exercise.path))
- })
- .min()
- {
- self.current_exercise_ind = current_exercise_ind;
- } else {
- return Ok(());
- };
-
- while self.current_exercise_ind < self.exercises.len() {
- self.exercise = &self.exercises[self.current_exercise_ind];
- if !self.run_exercise()? {
- break;
- }
-
- self.current_exercise_ind += 1;
- }
-
- Ok(())
- }
-
- fn prompt(&mut self) -> io::Result<()> {
- self.writer.write_all(&self.prompt)?;
- self.writer.flush()
- }
-
- fn render(&mut self) -> Result<()> {
- self.writer.execute(Clear(ClearType::All))?;
-
- if let Some(stdout) = &self.stdout {
- self.writer.write_all(stdout)?;
- }
-
- if let Some(stderr) = &self.stderr {
- self.writer.write_all(stderr)?;
- }
-
- if let Some(message) = &self.message {
- self.writer.write_all(message.as_bytes())?;
- }
-
- self.prompt()?;
-
- Ok(())
- }
-}
-
pub fn watch(state_file: &StateFile, exercises: &[Exercise]) -> Result<()> {
let (tx, rx) = channel();
let mut debouncer = new_debouncer(Duration::from_secs(1), tx)?;
@@ -158,29 +27,7 @@ pub fn watch(state_file: &StateFile, exercises: &[Exercise]) -> Result<()> {
.watcher()
.watch(Path::new("exercises"), RecursiveMode::Recursive)?;
- let current_exercise_ind = state_file.next_exercise_ind();
-
- let exercise = &exercises[current_exercise_ind];
-
- let writer = io::stdout().lock();
-
- let mut watch_state = WatchState {
- writer,
- rx,
- exercises,
- exercise,
- current_exercise_ind,
- stdout: None,
- stderr: None,
- message: None,
- prompt: format!(
- "\n\n{}int/{}lear/{}uit? ",
- "h".bold(),
- "c".bold(),
- "q".bold()
- )
- .into_bytes(),
- };
+ let mut watch_state = WatchState::new(state_file, exercises, rx);
watch_state.run_exercise()?;
watch_state.render()?;
@@ -214,24 +61,20 @@ pub fn watch(state_file: &StateFile, exercises: &[Exercise]) -> Result<()> {
if let Ok(event) = rx.try_recv() {
match event {
Some(Event::Hint) => {
- watch_state
- .writer
- .write_all(watch_state.exercise.hint.as_bytes())?;
- watch_state.prompt()?;
+ watch_state.show_hint()?;
}
Some(Event::Clear) => {
watch_state.render()?;
}
Some(Event::Quit) => break,
None => {
- watch_state.writer.write_all(b"Invalid command")?;
- watch_state.prompt()?;
+ watch_state.handle_invalid_cmd()?;
}
}
}
}
- watch_state.writer.write_all(b"
+ watch_state.into_writer().write_all(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.
")?;