diff --git a/src/app_state.rs b/src/app_state.rs index 6f393bc5..33d3de2b 100644 --- a/src/app_state.rs +++ b/src/app_state.rs @@ -257,6 +257,41 @@ impl AppState { } } + pub fn current_solution_path(&self) -> Result> { + if DEBUG_PROFILE { + return Ok(None); + } + + let current_exercise = self.current_exercise(); + + if self.official_exercises { + let dir_name = current_exercise + .dir + .context("Official exercises must be nested in the `exercises` directory")?; + let solution_path = format!("solutions/{dir_name}/{}.rs", current_exercise.name); + + EMBEDDED_FILES.write_solution_to_disk( + self.current_exercise_ind, + dir_name, + &solution_path, + )?; + + Ok(Some(solution_path)) + } else { + let solution_path = if let Some(dir) = current_exercise.dir { + format!("solutions/{dir}/{}.rs", current_exercise.name) + } else { + format!("solutions/{}.rs", current_exercise.name) + }; + + if Path::new(&solution_path).exists() { + return Ok(Some(solution_path)); + } + + Ok(None) + } + } + pub fn done_current_exercise(&mut self, writer: &mut StdoutLock) -> Result { let exercise = &mut self.exercises[self.current_exercise_ind]; if !exercise.done { @@ -264,16 +299,6 @@ impl AppState { self.n_done += 1; } - if self.official_exercises && !DEBUG_PROFILE { - EMBEDDED_FILES.write_solution_to_disk( - self.current_exercise_ind, - exercise - .dir - .context("Official exercises must be nested in the `exercises` directory")?, - exercise.name, - )?; - } - let Some(ind) = self.next_pending_exercise_ind() else { writer.write_all(RERUNNING_ALL_EXERCISES_MSG)?; diff --git a/src/embedded.rs b/src/embedded.rs index 756b4141..d7952a1f 100644 --- a/src/embedded.rs +++ b/src/embedded.rs @@ -113,14 +113,12 @@ impl EmbeddedFiles { &self, exercise_ind: usize, dir_name: &str, - exercise_name: &str, + path: &str, ) -> Result<()> { let dir_path = format!("solutions/{dir_name}"); - create_dir_all(&dir_path).context("Failed to create the directory {dir_path}")?; + create_dir_all(&dir_path) + .with_context(|| format!("Failed to create the directory {dir_path}"))?; - WriteStrategy::Overwrite.write( - &format!("{dir_path}/{exercise_name}.rs"), - self.exercise_files[exercise_ind].solution, - ) + WriteStrategy::Overwrite.write(path, self.exercise_files[exercise_ind].solution) } } diff --git a/src/exercise.rs b/src/exercise.rs index 4493a575..45ac2086 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -2,33 +2,11 @@ use anyhow::{Context, Result}; use crossterm::style::{style, StyledContent, Stylize}; use std::{ fmt::{self, Display, Formatter}, - fs, path::Path, process::{Command, Output}, }; -use crate::{info_file::Mode, DEBUG_PROFILE}; - -pub struct TerminalFileLink<'a> { - path: &'a str, -} - -impl<'a> Display for TerminalFileLink<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Ok(Some(canonical_path)) = fs::canonicalize(self.path) - .as_deref() - .map(|path| path.to_str()) - { - write!( - f, - "\x1b]8;;file://{}\x1b\\{}\x1b]8;;\x1b\\", - canonical_path, self.path, - ) - } else { - write!(f, "{}", self.path,) - } - } -} +use crate::{info_file::Mode, terminal_link::TerminalFileLink, DEBUG_PROFILE}; pub struct Exercise { pub dir: Option<&'static str>, @@ -85,9 +63,7 @@ impl Exercise { } pub fn terminal_link(&self) -> StyledContent> { - style(TerminalFileLink { path: self.path }) - .underlined() - .blue() + style(TerminalFileLink(self.path)).underlined().blue() } } diff --git a/src/main.rs b/src/main.rs index 9ff218a3..790fff69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ mod init; mod list; mod progress_bar; mod run; +mod terminal_link; mod watch; const CURRENT_FORMAT_VERSION: u8 = 1; diff --git a/src/run.rs b/src/run.rs index 863b584e..a2b69720 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,8 +1,11 @@ use anyhow::{bail, Result}; -use crossterm::style::Stylize; +use crossterm::style::{style, Stylize}; use std::io::{self, Write}; -use crate::app_state::{AppState, ExercisesProgress}; +use crate::{ + app_state::{AppState, ExercisesProgress}, + terminal_link::TerminalFileLink, +}; pub fn run(app_state: &mut AppState) -> Result<()> { let exercise = app_state.current_exercise(); @@ -29,6 +32,13 @@ pub fn run(app_state: &mut AppState) -> Result<()> { exercise.path.green(), ))?; + if let Some(solution_path) = app_state.current_solution_path()? { + println!( + "\nA solution file can be found at {}\n", + style(TerminalFileLink(&solution_path)).underlined().green(), + ); + } + match app_state.done_current_exercise(&mut stdout)? { ExercisesProgress::AllDone => (), ExercisesProgress::Pending => println!( diff --git a/src/terminal_link.rs b/src/terminal_link.rs new file mode 100644 index 00000000..c9e6bced --- /dev/null +++ b/src/terminal_link.rs @@ -0,0 +1,23 @@ +use std::{ + fmt::{self, Display, Formatter}, + fs, +}; + +pub struct TerminalFileLink<'a>(pub &'a str); + +impl<'a> Display for TerminalFileLink<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Ok(Some(canonical_path)) = fs::canonicalize(self.0) + .as_deref() + .map(|path| path.to_str()) + { + write!( + f, + "\x1b]8;;file://{}\x1b\\{}\x1b]8;;\x1b\\", + canonical_path, self.0, + ) + } else { + write!(f, "{}", self.0) + } + } +} diff --git a/src/watch/state.rs b/src/watch/state.rs index c0f6c532..5f4abf3c 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -1,6 +1,6 @@ use anyhow::Result; use crossterm::{ - style::Stylize, + style::{style, Stylize}, terminal::{size, Clear, ClearType}, ExecutableCommand, }; @@ -9,15 +9,22 @@ use std::io::{self, StdoutLock, Write}; use crate::{ app_state::{AppState, ExercisesProgress}, progress_bar::progress_bar, + terminal_link::TerminalFileLink, }; +enum DoneStatus { + DoneWithSolution(String), + DoneWithoutSolution, + Pending, +} + pub struct WatchState<'a> { writer: StdoutLock<'a>, app_state: &'a mut AppState, stdout: Option>, stderr: Option>, show_hint: bool, - show_done: bool, + done_status: DoneStatus, manual_run: bool, } @@ -31,7 +38,7 @@ impl<'a> WatchState<'a> { stdout: None, stderr: None, show_hint: false, - show_done: false, + done_status: DoneStatus::Pending, manual_run, } } @@ -49,13 +56,18 @@ impl<'a> WatchState<'a> { if output.status.success() { self.stderr = None; - self.show_done = true; + self.done_status = + if let Some(solution_path) = self.app_state.current_solution_path()? { + DoneStatus::DoneWithSolution(solution_path) + } else { + DoneStatus::DoneWithoutSolution + }; } else { self.app_state .set_pending(self.app_state.current_exercise_ind())?; self.stderr = Some(output.stderr); - self.show_done = false; + self.done_status = DoneStatus::Pending; } self.render() @@ -67,7 +79,7 @@ impl<'a> WatchState<'a> { } pub fn next_exercise(&mut self) -> Result { - if !self.show_done { + if matches!(self.done_status, DoneStatus::Pending) { self.writer .write_all(b"The current exercise isn't done yet\n")?; self.show_prompt()?; @@ -84,7 +96,7 @@ impl<'a> WatchState<'a> { self.writer.write_fmt(format_args!("{}un/", 'r'.bold()))?; } - if self.show_done { + if !matches!(self.done_status, DoneStatus::Pending) { self.writer.write_fmt(format_args!("{}ext/", 'n'.bold()))?; } @@ -124,7 +136,7 @@ impl<'a> WatchState<'a> { ))?; } - if self.show_done { + if !matches!(self.done_status, DoneStatus::Pending) { self.writer.write_fmt(format_args!( "{}\n\n", "Exercise done ✓ @@ -134,6 +146,13 @@ When you are done experimenting, enter `n` or `next` to go to the next exercise ))?; } + if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status { + self.writer.write_fmt(format_args!( + "A solution file can be found at {}\n\n", + style(TerminalFileLink(solution_path)).underlined().green() + ))?; + } + let line_width = size()?.0; let progress_bar = progress_bar( self.app_state.n_done(),