diff --git a/src/app_state.rs b/src/app_state.rs
index 1a051b97..98c63842 100644
--- a/src/app_state.rs
+++ b/src/app_state.rs
@@ -38,7 +38,7 @@ impl AppState {
// Leaking to be able to borrow in the watch mode `Table`.
// Leaking is not a problem because the `AppState` instance lives until
// the end of the program.
- let path = Box::leak(exercise_info.path().into_boxed_path());
+ let path = exercise_info.path().leak();
exercise_info.name.shrink_to_fit();
let name = exercise_info.name.leak();
diff --git a/src/app_state/state_file.rs b/src/app_state/state_file.rs
index 364a1fa3..4e4a0e15 100644
--- a/src/app_state/state_file.rs
+++ b/src/app_state/state_file.rs
@@ -59,7 +59,7 @@ pub fn write(app_state: &AppState) -> Result<()> {
exercises: ExercisesStateSerializer(&app_state.exercises),
};
- let mut buf = Vec::with_capacity(1024);
+ let mut buf = Vec::with_capacity(4096);
serde_json::ser::to_writer(&mut buf, &content).context("Failed to serialize the state")?;
fs::write(STATE_FILE_NAME, buf)
.with_context(|| format!("Failed to write the state file `{STATE_FILE_NAME}`"))?;
@@ -69,8 +69,6 @@ pub fn write(app_state: &AppState) -> Result<()> {
#[cfg(test)]
mod tests {
- use std::path::Path;
-
use crate::info_file::Mode;
use super::*;
@@ -81,14 +79,14 @@ mod tests {
let exercises = [
Exercise {
name: "1",
- path: Path::new("exercises/1.rs"),
+ path: "exercises/1.rs",
mode: Mode::Run,
hint: String::new(),
done: true,
},
Exercise {
name: "2",
- path: Path::new("exercises/2.rs"),
+ path: "exercises/2.rs",
mode: Mode::Test,
hint: String::new(),
done: false,
diff --git a/src/embedded.rs b/src/embedded.rs
index 1e2d6770..866b12b8 100644
--- a/src/embedded.rs
+++ b/src/embedded.rs
@@ -91,7 +91,12 @@ impl EmbeddedFiles {
Ok(())
}
- pub fn write_exercise_to_disk(&self, path: &Path, strategy: WriteStrategy) -> io::Result<()> {
+ pub fn write_exercise_to_disk
(&self, path: P, strategy: WriteStrategy) -> io::Result<()>
+ where
+ P: AsRef,
+ {
+ let path = path.as_ref();
+
if let Some(file) = self
.exercises_dir
.files
diff --git a/src/exercise.rs b/src/exercise.rs
index c5ece5f5..2ec8d979 100644
--- a/src/exercise.rs
+++ b/src/exercise.rs
@@ -1,7 +1,8 @@
use anyhow::{Context, Result};
+use crossterm::style::{style, StyledContent, Stylize};
use std::{
fmt::{self, Display, Formatter},
- path::Path,
+ fs,
process::{Command, Output},
};
@@ -10,11 +11,32 @@ use crate::{
info_file::Mode,
};
+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,)
+ }
+ }
+}
+
pub struct Exercise {
// Exercise's unique name
pub name: &'static str,
// Exercise's path
- pub path: &'static Path,
+ pub path: &'static str,
// The mode of the exercise
pub mode: Mode,
// The hint text associated with the exercise
@@ -60,10 +82,16 @@ impl Exercise {
.write_exercise_to_disk(self.path, WriteStrategy::Overwrite)
.with_context(|| format!("Failed to reset the exercise {self}"))
}
+
+ pub fn terminal_link(&self) -> StyledContent> {
+ style(TerminalFileLink { path: self.path })
+ .underlined()
+ .blue()
+ }
}
impl Display for Exercise {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- Display::fmt(&self.path.display(), f)
+ self.path.fmt(f)
}
}
diff --git a/src/info_file.rs b/src/info_file.rs
index dc97b926..2a45e02d 100644
--- a/src/info_file.rs
+++ b/src/info_file.rs
@@ -1,6 +1,6 @@
use anyhow::{bail, Context, Error, Result};
use serde::Deserialize;
-use std::{fs, path::PathBuf};
+use std::fs;
// The mode of the exercise.
#[derive(Deserialize, Copy, Clone)]
@@ -28,14 +28,12 @@ pub struct ExerciseInfo {
}
impl ExerciseInfo {
- pub fn path(&self) -> PathBuf {
- let path = if let Some(dir) = &self.dir {
+ pub fn path(&self) -> String {
+ if let Some(dir) = &self.dir {
format!("exercises/{dir}/{}.rs", self.name)
} else {
format!("exercises/{}.rs", self.name)
- };
-
- PathBuf::from(path)
+ }
}
}
diff --git a/src/list/state.rs b/src/list/state.rs
index 38391a49..2a1fef18 100644
--- a/src/list/state.rs
+++ b/src/list/state.rs
@@ -63,7 +63,7 @@ impl<'a> UiState<'a> {
next,
exercise_state,
Span::raw(exercise.name),
- Span::raw(exercise.path.to_string_lossy()),
+ Span::raw(exercise.path),
]))
});
diff --git a/src/run.rs b/src/run.rs
index 9c504b53..863b584e 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -17,18 +17,24 @@ pub fn run(app_state: &mut AppState) -> Result<()> {
if !output.status.success() {
app_state.set_pending(app_state.current_exercise_ind())?;
- bail!("Ran {} with errors", app_state.current_exercise());
+ bail!(
+ "Ran {} with errors",
+ app_state.current_exercise().terminal_link(),
+ );
}
stdout.write_fmt(format_args!(
"{}{}\n",
"✓ Successfully ran ".green(),
- exercise.path.to_string_lossy().green(),
+ exercise.path.green(),
))?;
match app_state.done_current_exercise(&mut stdout)? {
ExercisesProgress::AllDone => (),
- ExercisesProgress::Pending => println!("Next exercise: {}", app_state.current_exercise()),
+ ExercisesProgress::Pending => println!(
+ "Next exercise: {}",
+ app_state.current_exercise().terminal_link(),
+ ),
}
Ok(())
diff --git a/src/watch.rs b/src/watch.rs
index 58e829f3..bab64ae1 100644
--- a/src/watch.rs
+++ b/src/watch.rs
@@ -42,7 +42,7 @@ pub enum WatchExit {
pub fn watch(
app_state: &mut AppState,
- exercise_paths: &'static [&'static Path],
+ exercise_paths: &'static [&'static str],
) -> Result {
let (tx, rx) = channel();
let mut debouncer = new_debouncer(
diff --git a/src/watch/notify_event.rs b/src/watch/notify_event.rs
index 0c8d6692..fb9a8c05 100644
--- a/src/watch/notify_event.rs
+++ b/src/watch/notify_event.rs
@@ -1,11 +1,11 @@
use notify_debouncer_mini::{DebounceEventResult, DebouncedEventKind};
-use std::{path::Path, sync::mpsc::Sender};
+use std::sync::mpsc::Sender;
use super::WatchEvent;
pub struct DebounceEventHandler {
pub tx: Sender,
- pub exercise_paths: &'static [&'static Path],
+ pub exercise_paths: &'static [&'static str],
}
impl notify_debouncer_mini::DebounceEventHandler for DebounceEventHandler {
diff --git a/src/watch/state.rs b/src/watch/state.rs
index 6a97637b..1a79573f 100644
--- a/src/watch/state.rs
+++ b/src/watch/state.rs
@@ -136,11 +136,7 @@ When you are done experimenting, enter `n` or `next` to go to the next exercise
)?;
self.writer.write_fmt(format_args!(
"{progress_bar}Current exercise: {}\n",
- self.app_state
- .current_exercise()
- .path
- .to_string_lossy()
- .bold(),
+ self.app_state.current_exercise().terminal_link(),
))?;
self.show_prompt()?;