mirror of
https://github.com/notohh/rustlings.git
synced 2024-11-21 21:42:23 -05:00
Add terminal links
This commit is contained in:
parent
5c0073a948
commit
bee62c89de
10 changed files with 59 additions and 28 deletions
|
@ -38,7 +38,7 @@ impl AppState {
|
||||||
// Leaking to be able to borrow in the watch mode `Table`.
|
// Leaking to be able to borrow in the watch mode `Table`.
|
||||||
// Leaking is not a problem because the `AppState` instance lives until
|
// Leaking is not a problem because the `AppState` instance lives until
|
||||||
// the end of the program.
|
// 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();
|
exercise_info.name.shrink_to_fit();
|
||||||
let name = exercise_info.name.leak();
|
let name = exercise_info.name.leak();
|
||||||
|
|
|
@ -59,7 +59,7 @@ pub fn write(app_state: &AppState) -> Result<()> {
|
||||||
exercises: ExercisesStateSerializer(&app_state.exercises),
|
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")?;
|
serde_json::ser::to_writer(&mut buf, &content).context("Failed to serialize the state")?;
|
||||||
fs::write(STATE_FILE_NAME, buf)
|
fs::write(STATE_FILE_NAME, buf)
|
||||||
.with_context(|| format!("Failed to write the state file `{STATE_FILE_NAME}`"))?;
|
.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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use crate::info_file::Mode;
|
use crate::info_file::Mode;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -81,14 +79,14 @@ mod tests {
|
||||||
let exercises = [
|
let exercises = [
|
||||||
Exercise {
|
Exercise {
|
||||||
name: "1",
|
name: "1",
|
||||||
path: Path::new("exercises/1.rs"),
|
path: "exercises/1.rs",
|
||||||
mode: Mode::Run,
|
mode: Mode::Run,
|
||||||
hint: String::new(),
|
hint: String::new(),
|
||||||
done: true,
|
done: true,
|
||||||
},
|
},
|
||||||
Exercise {
|
Exercise {
|
||||||
name: "2",
|
name: "2",
|
||||||
path: Path::new("exercises/2.rs"),
|
path: "exercises/2.rs",
|
||||||
mode: Mode::Test,
|
mode: Mode::Test,
|
||||||
hint: String::new(),
|
hint: String::new(),
|
||||||
done: false,
|
done: false,
|
||||||
|
|
|
@ -91,7 +91,12 @@ impl EmbeddedFiles {
|
||||||
Ok(())
|
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
|
if let Some(file) = self
|
||||||
.exercises_dir
|
.exercises_dir
|
||||||
.files
|
.files
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use crossterm::style::{style, StyledContent, Stylize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Display, Formatter},
|
fmt::{self, Display, Formatter},
|
||||||
path::Path,
|
fs,
|
||||||
process::{Command, Output},
|
process::{Command, Output},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,11 +11,32 @@ use crate::{
|
||||||
info_file::Mode,
|
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 {
|
pub struct Exercise {
|
||||||
// Exercise's unique name
|
// Exercise's unique name
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
// Exercise's path
|
// Exercise's path
|
||||||
pub path: &'static Path,
|
pub path: &'static str,
|
||||||
// The mode of the exercise
|
// The mode of the exercise
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
// The hint text associated with the exercise
|
// The hint text associated with the exercise
|
||||||
|
@ -60,10 +82,16 @@ impl Exercise {
|
||||||
.write_exercise_to_disk(self.path, WriteStrategy::Overwrite)
|
.write_exercise_to_disk(self.path, WriteStrategy::Overwrite)
|
||||||
.with_context(|| format!("Failed to reset the exercise {self}"))
|
.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 {
|
impl Display for Exercise {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
Display::fmt(&self.path.display(), f)
|
self.path.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use anyhow::{bail, Context, Error, Result};
|
use anyhow::{bail, Context, Error, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{fs, path::PathBuf};
|
use std::fs;
|
||||||
|
|
||||||
// The mode of the exercise.
|
// The mode of the exercise.
|
||||||
#[derive(Deserialize, Copy, Clone)]
|
#[derive(Deserialize, Copy, Clone)]
|
||||||
|
@ -28,14 +28,12 @@ pub struct ExerciseInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExerciseInfo {
|
impl ExerciseInfo {
|
||||||
pub fn path(&self) -> PathBuf {
|
pub fn path(&self) -> String {
|
||||||
let path = if let Some(dir) = &self.dir {
|
if let Some(dir) = &self.dir {
|
||||||
format!("exercises/{dir}/{}.rs", self.name)
|
format!("exercises/{dir}/{}.rs", self.name)
|
||||||
} else {
|
} else {
|
||||||
format!("exercises/{}.rs", self.name)
|
format!("exercises/{}.rs", self.name)
|
||||||
};
|
}
|
||||||
|
|
||||||
PathBuf::from(path)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ impl<'a> UiState<'a> {
|
||||||
next,
|
next,
|
||||||
exercise_state,
|
exercise_state,
|
||||||
Span::raw(exercise.name),
|
Span::raw(exercise.name),
|
||||||
Span::raw(exercise.path.to_string_lossy()),
|
Span::raw(exercise.path),
|
||||||
]))
|
]))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
12
src/run.rs
12
src/run.rs
|
@ -17,18 +17,24 @@ pub fn run(app_state: &mut AppState) -> Result<()> {
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
app_state.set_pending(app_state.current_exercise_ind())?;
|
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!(
|
stdout.write_fmt(format_args!(
|
||||||
"{}{}\n",
|
"{}{}\n",
|
||||||
"✓ Successfully ran ".green(),
|
"✓ Successfully ran ".green(),
|
||||||
exercise.path.to_string_lossy().green(),
|
exercise.path.green(),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
match app_state.done_current_exercise(&mut stdout)? {
|
match app_state.done_current_exercise(&mut stdout)? {
|
||||||
ExercisesProgress::AllDone => (),
|
ExercisesProgress::AllDone => (),
|
||||||
ExercisesProgress::Pending => println!("Next exercise: {}", app_state.current_exercise()),
|
ExercisesProgress::Pending => println!(
|
||||||
|
"Next exercise: {}",
|
||||||
|
app_state.current_exercise().terminal_link(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -42,7 +42,7 @@ pub enum WatchExit {
|
||||||
|
|
||||||
pub fn watch(
|
pub fn watch(
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
exercise_paths: &'static [&'static Path],
|
exercise_paths: &'static [&'static str],
|
||||||
) -> Result<WatchExit> {
|
) -> Result<WatchExit> {
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
let mut debouncer = new_debouncer(
|
let mut debouncer = new_debouncer(
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use notify_debouncer_mini::{DebounceEventResult, DebouncedEventKind};
|
use notify_debouncer_mini::{DebounceEventResult, DebouncedEventKind};
|
||||||
use std::{path::Path, sync::mpsc::Sender};
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
use super::WatchEvent;
|
use super::WatchEvent;
|
||||||
|
|
||||||
pub struct DebounceEventHandler {
|
pub struct DebounceEventHandler {
|
||||||
pub tx: Sender<WatchEvent>,
|
pub tx: Sender<WatchEvent>,
|
||||||
pub exercise_paths: &'static [&'static Path],
|
pub exercise_paths: &'static [&'static str],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl notify_debouncer_mini::DebounceEventHandler for DebounceEventHandler {
|
impl notify_debouncer_mini::DebounceEventHandler for DebounceEventHandler {
|
||||||
|
|
|
@ -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!(
|
self.writer.write_fmt(format_args!(
|
||||||
"{progress_bar}Current exercise: {}\n",
|
"{progress_bar}Current exercise: {}\n",
|
||||||
self.app_state
|
self.app_state.current_exercise().terminal_link(),
|
||||||
.current_exercise()
|
|
||||||
.path
|
|
||||||
.to_string_lossy()
|
|
||||||
.bold(),
|
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
self.show_prompt()?;
|
self.show_prompt()?;
|
||||||
|
|
Loading…
Reference in a new issue