Add terminal links

This commit is contained in:
mo8it 2024-04-14 02:41:19 +02:00
parent 5c0073a948
commit bee62c89de
10 changed files with 59 additions and 28 deletions

View file

@ -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();

View file

@ -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,

View file

@ -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<P>(&self, path: P, strategy: WriteStrategy) -> io::Result<()>
where
P: AsRef<Path>,
{
let path = path.as_ref();
if let Some(file) = self
.exercises_dir
.files

View file

@ -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<TerminalFileLink<'_>> {
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)
}
}

View file

@ -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)
}
}
}

View file

@ -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),
]))
});

View file

@ -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(())

View file

@ -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<WatchExit> {
let (tx, rx) = channel();
let mut debouncer = new_debouncer(

View file

@ -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<WatchEvent>,
pub exercise_paths: &'static [&'static Path],
pub exercise_paths: &'static [&'static str],
}
impl notify_debouncer_mini::DebounceEventHandler for DebounceEventHandler {

View file

@ -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()?;