summaryrefslogtreecommitdiff
path: root/src/tui.rs
diff options
context:
space:
mode:
authormo8it <mo8it@proton.me>2024-04-05 03:04:53 +0200
committermo8it <mo8it@proton.me>2024-04-05 03:04:53 +0200
commitb0f19fd862d659d2d4b01f2faa6b006fe2c60561 (patch)
tree7756232e7feb4c0e159bf7e3ecf8d76d68ab141c /src/tui.rs
parent0bf51c6a0de117d7f28ddf4a253bfc0306f2e78b (diff)
Start with the TUI
Diffstat (limited to 'src/tui.rs')
-rw-r--r--src/tui.rs92
1 files changed, 92 insertions, 0 deletions
diff --git a/src/tui.rs b/src/tui.rs
new file mode 100644
index 0000000..bb87365
--- /dev/null
+++ b/src/tui.rs
@@ -0,0 +1,92 @@
+use anyhow::Result;
+use crossterm::{
+ terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
+ ExecutableCommand,
+};
+use notify_debouncer_mini::{new_debouncer, notify::RecursiveMode, DebouncedEventKind};
+use ratatui::{backend::CrosstermBackend, Terminal};
+use std::{
+ io::stdout,
+ path::Path,
+ sync::mpsc::{channel, RecvTimeoutError},
+ time::Duration,
+};
+
+use crate::{
+ exercise::Exercise,
+ verify::{verify, VerifyState},
+};
+
+fn watch(exercises: &[Exercise]) -> Result<()> {
+ let (tx, rx) = channel();
+
+ let mut debouncer = new_debouncer(Duration::from_secs(1), tx)?;
+ debouncer
+ .watcher()
+ .watch(Path::new("exercises"), RecursiveMode::Recursive)?;
+
+ let mut failed_exercise_hint = match verify(exercises, (0, exercises.len()))? {
+ VerifyState::AllExercisesDone => return Ok(()),
+ VerifyState::Failed(exercise) => Some(&exercise.hint),
+ };
+
+ let mut pending_exercises = Vec::with_capacity(exercises.len());
+ loop {
+ match rx.recv_timeout(Duration::from_secs(1)) {
+ Ok(event) => match event {
+ Ok(events) => {
+ for event in events {
+ if event.kind == DebouncedEventKind::Any
+ && event.path.extension().is_some_and(|ext| ext == "rs")
+ {
+ pending_exercises.extend(exercises.iter().filter(|exercise| {
+ !exercise.looks_done().unwrap_or(false)
+ || event.path.ends_with(&exercise.path)
+ }));
+ let num_done = exercises.len() - pending_exercises.len();
+
+ match verify(
+ pending_exercises.iter().copied(),
+ (num_done, exercises.len()),
+ )? {
+ VerifyState::AllExercisesDone => return Ok(()),
+ VerifyState::Failed(exercise) => {
+ failed_exercise_hint = Some(&exercise.hint);
+ }
+ }
+
+ pending_exercises.clear();
+ }
+ }
+ }
+ Err(e) => println!("watch error: {e:?}"),
+ },
+ Err(RecvTimeoutError::Timeout) => {
+ // the timeout expired, just check the `should_quit` variable below then loop again
+ }
+ Err(e) => println!("watch error: {e:?}"),
+ }
+
+ // TODO: Check if we need to exit
+ }
+}
+
+pub fn tui(exercises: &[Exercise]) -> Result<()> {
+ let mut stdout = stdout().lock();
+ stdout.execute(EnterAlternateScreen)?;
+ enable_raw_mode()?;
+ let mut terminal = Terminal::new(CrosstermBackend::new(&mut stdout))?;
+ terminal.clear()?;
+
+ watch(exercises)?;
+
+ drop(terminal);
+ stdout.execute(LeaveAlternateScreen)?;
+ disable_raw_mode()?;
+
+ // TODO
+ println!("We hope you're enjoying learning about Rust!");
+ println!("If you want to continue working on the exercises at a later point, you can simply run `rustlings watch` again");
+
+ Ok(())
+}