summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormo8it <mo8it@proton.me>2025-08-21 22:43:46 +0200
committermo8it <mo8it@proton.me>2025-08-21 23:15:48 +0200
commit2d1d531550afaac36dbf4d15c383bc0e1cbf248d (patch)
tree67f030f84414a463e2e7958b1267baa3a14487e6
parenta712e484d09ce27a622da5e61d26bbb1004f51d2 (diff)
Fix file links in VS Code
-rw-r--r--src/app_state.rs12
-rw-r--r--src/exercise.rs38
-rw-r--r--src/list/state.rs8
-rw-r--r--src/main.rs2
-rw-r--r--src/run.rs6
-rw-r--r--src/term.rs29
-rw-r--r--src/watch/state.rs4
7 files changed, 57 insertions, 42 deletions
diff --git a/src/app_state.rs b/src/app_state.rs
index f3f3481..d654d04 100644
--- a/src/app_state.rs
+++ b/src/app_state.rs
@@ -60,8 +60,7 @@ pub struct AppState {
file_buf: Vec<u8>,
official_exercises: bool,
cmd_runner: CmdRunner,
- // Running in VS Code.
- vs_code: bool,
+ emit_file_links: bool,
}
impl AppState {
@@ -181,7 +180,8 @@ impl AppState {
file_buf,
official_exercises: !Path::new("info.toml").exists(),
cmd_runner,
- vs_code: env::var_os("TERM_PROGRAM").is_some_and(|v| v == "vscode"),
+ // VS Code has its own file link handling
+ emit_file_links: env::var_os("TERM_PROGRAM").is_none_or(|v| v != "vscode"),
};
Ok((slf, state_file_status))
@@ -218,8 +218,8 @@ impl AppState {
}
#[inline]
- pub fn vs_code(&self) -> bool {
- self.vs_code
+ pub fn emit_file_links(&self) -> bool {
+ self.emit_file_links
}
// Write the state file.
@@ -621,7 +621,7 @@ mod tests {
file_buf: Vec::new(),
official_exercises: true,
cmd_runner: CmdRunner::build().unwrap(),
- vs_code: false,
+ emit_file_links: true,
};
let mut assert = |done: [bool; 3], expected: [Option<usize>; 3]| {
diff --git a/src/exercise.rs b/src/exercise.rs
index fdfbc4f..6f517be 100644
--- a/src/exercise.rs
+++ b/src/exercise.rs
@@ -7,22 +7,28 @@ use std::io::{self, StdoutLock, Write};
use crate::{
cmd::CmdRunner,
- term::{self, CountedWrite, terminal_file_link, write_ansi},
+ term::{self, CountedWrite, file_path, terminal_file_link, write_ansi},
};
/// The initial capacity of the output buffer.
pub const OUTPUT_CAPACITY: usize = 1 << 14;
-pub fn solution_link_line(stdout: &mut StdoutLock, solution_path: &str) -> io::Result<()> {
+pub fn solution_link_line(
+ stdout: &mut StdoutLock,
+ solution_path: &str,
+ emit_file_links: bool,
+) -> io::Result<()> {
stdout.queue(SetAttribute(Attribute::Bold))?;
stdout.write_all(b"Solution")?;
stdout.queue(ResetColor)?;
stdout.write_all(b" for comparison: ")?;
- if let Some(canonical_path) = term::canonicalize(solution_path) {
- terminal_file_link(stdout, solution_path, &canonical_path, Color::Cyan)?;
- } else {
- stdout.write_all(solution_path.as_bytes())?;
- }
+ file_path(stdout, Color::Cyan, |writer| {
+ if emit_file_links && let Some(canonical_path) = term::canonicalize(solution_path) {
+ terminal_file_link(writer, solution_path, &canonical_path)
+ } else {
+ writer.stdout().write_all(solution_path.as_bytes())
+ }
+ })?;
stdout.write_all(b"\n")
}
@@ -72,12 +78,18 @@ pub struct Exercise {
}
impl Exercise {
- pub fn terminal_file_link<'a>(&self, writer: &mut impl CountedWrite<'a>) -> io::Result<()> {
- if let Some(canonical_path) = self.canonical_path.as_deref() {
- return terminal_file_link(writer, self.path, canonical_path, Color::Blue);
- }
-
- writer.write_str(self.path)
+ pub fn terminal_file_link<'a>(
+ &self,
+ writer: &mut impl CountedWrite<'a>,
+ emit_file_links: bool,
+ ) -> io::Result<()> {
+ file_path(writer, Color::Blue, |writer| {
+ if emit_file_links && let Some(canonical_path) = self.canonical_path.as_deref() {
+ terminal_file_link(writer, self.path, canonical_path)
+ } else {
+ writer.write_str(self.path)
+ }
+ })
}
}
diff --git a/src/list/state.rs b/src/list/state.rs
index ae65ec2..50d06be 100644
--- a/src/list/state.rs
+++ b/src/list/state.rs
@@ -186,13 +186,7 @@ impl<'a> ListState<'a> {
writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?;
- // The list links aren't shown correctly in VS Code on Windows.
- // But VS Code shows its own links anyway.
- if self.app_state.vs_code() {
- writer.write_str(exercise.path)?;
- } else {
- exercise.terminal_file_link(&mut writer)?;
- }
+ exercise.terminal_file_link(&mut writer, self.app_state.emit_file_links())?;
writer.write_ascii(&self.path_col_padding[exercise.path.len()..])?;
diff --git a/src/main.rs b/src/main.rs
index 29de56b..ffd2dfa 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -167,7 +167,7 @@ fn main() -> Result<ExitCode> {
}
app_state
.current_exercise()
- .terminal_file_link(&mut stdout)?;
+ .terminal_file_link(&mut stdout, app_state.emit_file_links())?;
stdout.write_all(b"\n")?;
return Ok(ExitCode::FAILURE);
diff --git a/src/run.rs b/src/run.rs
index 6f4f099..b473fc2 100644
--- a/src/run.rs
+++ b/src/run.rs
@@ -27,7 +27,7 @@ pub fn run(app_state: &mut AppState) -> Result<ExitCode> {
stdout.write_all(b"Ran ")?;
app_state
.current_exercise()
- .terminal_file_link(&mut stdout)?;
+ .terminal_file_link(&mut stdout, app_state.emit_file_links())?;
stdout.write_all(b" with errors\n")?;
return Ok(ExitCode::FAILURE);
@@ -41,7 +41,7 @@ pub fn run(app_state: &mut AppState) -> Result<ExitCode> {
if let Some(solution_path) = app_state.current_solution_path()? {
stdout.write_all(b"\n")?;
- solution_link_line(&mut stdout, &solution_path)?;
+ solution_link_line(&mut stdout, &solution_path, app_state.emit_file_links())?;
stdout.write_all(b"\n")?;
}
@@ -50,7 +50,7 @@ pub fn run(app_state: &mut AppState) -> Result<ExitCode> {
stdout.write_all(b"Next exercise: ")?;
app_state
.current_exercise()
- .terminal_file_link(&mut stdout)?;
+ .terminal_file_link(&mut stdout, app_state.emit_file_links())?;
stdout.write_all(b"\n")?;
}
ExercisesProgress::AllDone => (),
diff --git a/src/term.rs b/src/term.rs
index b7dcd9f..3d149b3 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -272,22 +272,18 @@ pub fn canonicalize(path: &str) -> Option<String> {
})
}
-pub fn terminal_file_link<'a>(
- writer: &mut impl CountedWrite<'a>,
- path: &str,
- canonical_path: &str,
+pub fn file_path<'a, W: CountedWrite<'a>>(
+ writer: &mut W,
color: Color,
+ f: impl FnOnce(&mut W) -> io::Result<()>,
) -> io::Result<()> {
writer
.stdout()
.queue(SetForegroundColor(color))?
.queue(SetAttribute(Attribute::Underlined))?;
- writer.stdout().write_all(b"\x1b]8;;file://")?;
- writer.stdout().write_all(canonical_path.as_bytes())?;
- writer.stdout().write_all(b"\x1b\\")?;
- // Only this part is visible.
- writer.write_str(path)?;
- writer.stdout().write_all(b"\x1b]8;;\x1b\\")?;
+
+ f(writer)?;
+
writer
.stdout()
.queue(SetForegroundColor(Color::Reset))?
@@ -296,6 +292,19 @@ pub fn terminal_file_link<'a>(
Ok(())
}
+pub fn terminal_file_link<'a>(
+ writer: &mut impl CountedWrite<'a>,
+ path: &str,
+ canonical_path: &str,
+) -> io::Result<()> {
+ writer.stdout().write_all(b"\x1b]8;;file://")?;
+ writer.stdout().write_all(canonical_path.as_bytes())?;
+ writer.stdout().write_all(b"\x1b\\")?;
+ // Only this part is visible.
+ writer.write_str(path)?;
+ writer.stdout().write_all(b"\x1b]8;;\x1b\\")
+}
+
pub fn write_ansi(output: &mut Vec<u8>, command: impl Command) {
struct FmtWriter<'a>(&'a mut Vec<u8>);
diff --git a/src/watch/state.rs b/src/watch/state.rs
index 2413bec..a92dd2d 100644
--- a/src/watch/state.rs
+++ b/src/watch/state.rs
@@ -233,7 +233,7 @@ impl<'a> WatchState<'a> {
stdout.write_all(b"\n")?;
if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status {
- solution_link_line(stdout, solution_path)?;
+ solution_link_line(stdout, solution_path, self.app_state.emit_file_links())?;
}
stdout.write_all(
@@ -252,7 +252,7 @@ impl<'a> WatchState<'a> {
stdout.write_all(b"\nCurrent exercise: ")?;
self.app_state
.current_exercise()
- .terminal_file_link(stdout)?;
+ .terminal_file_link(stdout, self.app_state.emit_file_links())?;
stdout.write_all(b"\n\n")?;
self.show_prompt(stdout)?;