1
0
mirror of https://github.com/rust-lang/rustlings.git synced 2025-09-16 09:26:49 +02:00

Fix file links in VS Code

This commit is contained in:
mo8it
2025-08-21 22:43:46 +02:00
parent a712e484d0
commit 2d1d531550
7 changed files with 57 additions and 42 deletions

View File

@@ -60,8 +60,7 @@ pub struct AppState {
file_buf: Vec<u8>, file_buf: Vec<u8>,
official_exercises: bool, official_exercises: bool,
cmd_runner: CmdRunner, cmd_runner: CmdRunner,
// Running in VS Code. emit_file_links: bool,
vs_code: bool,
} }
impl AppState { impl AppState {
@@ -181,7 +180,8 @@ impl AppState {
file_buf, file_buf,
official_exercises: !Path::new("info.toml").exists(), official_exercises: !Path::new("info.toml").exists(),
cmd_runner, 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)) Ok((slf, state_file_status))
@@ -218,8 +218,8 @@ impl AppState {
} }
#[inline] #[inline]
pub fn vs_code(&self) -> bool { pub fn emit_file_links(&self) -> bool {
self.vs_code self.emit_file_links
} }
// Write the state file. // Write the state file.
@@ -621,7 +621,7 @@ mod tests {
file_buf: Vec::new(), file_buf: Vec::new(),
official_exercises: true, official_exercises: true,
cmd_runner: CmdRunner::build().unwrap(), cmd_runner: CmdRunner::build().unwrap(),
vs_code: false, emit_file_links: true,
}; };
let mut assert = |done: [bool; 3], expected: [Option<usize>; 3]| { let mut assert = |done: [bool; 3], expected: [Option<usize>; 3]| {

View File

@@ -7,22 +7,28 @@ use std::io::{self, StdoutLock, Write};
use crate::{ use crate::{
cmd::CmdRunner, 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. /// The initial capacity of the output buffer.
pub const OUTPUT_CAPACITY: usize = 1 << 14; 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.queue(SetAttribute(Attribute::Bold))?;
stdout.write_all(b"Solution")?; stdout.write_all(b"Solution")?;
stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
stdout.write_all(b" for comparison: ")?; stdout.write_all(b" for comparison: ")?;
if let Some(canonical_path) = term::canonicalize(solution_path) { file_path(stdout, Color::Cyan, |writer| {
terminal_file_link(stdout, solution_path, &canonical_path, Color::Cyan)?; if emit_file_links && let Some(canonical_path) = term::canonicalize(solution_path) {
} else { terminal_file_link(writer, solution_path, &canonical_path)
stdout.write_all(solution_path.as_bytes())?; } else {
} writer.stdout().write_all(solution_path.as_bytes())
}
})?;
stdout.write_all(b"\n") stdout.write_all(b"\n")
} }
@@ -72,12 +78,18 @@ pub struct Exercise {
} }
impl Exercise { impl Exercise {
pub fn terminal_file_link<'a>(&self, writer: &mut impl CountedWrite<'a>) -> io::Result<()> { pub fn terminal_file_link<'a>(
if let Some(canonical_path) = self.canonical_path.as_deref() { &self,
return terminal_file_link(writer, self.path, canonical_path, Color::Blue); writer: &mut impl CountedWrite<'a>,
} emit_file_links: bool,
) -> io::Result<()> {
writer.write_str(self.path) 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)
}
})
} }
} }

View File

@@ -186,13 +186,7 @@ impl<'a> ListState<'a> {
writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?; writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?;
// The list links aren't shown correctly in VS Code on Windows. exercise.terminal_file_link(&mut writer, self.app_state.emit_file_links())?;
// 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)?;
}
writer.write_ascii(&self.path_col_padding[exercise.path.len()..])?; writer.write_ascii(&self.path_col_padding[exercise.path.len()..])?;

View File

@@ -167,7 +167,7 @@ fn main() -> Result<ExitCode> {
} }
app_state app_state
.current_exercise() .current_exercise()
.terminal_file_link(&mut stdout)?; .terminal_file_link(&mut stdout, app_state.emit_file_links())?;
stdout.write_all(b"\n")?; stdout.write_all(b"\n")?;
return Ok(ExitCode::FAILURE); return Ok(ExitCode::FAILURE);

View File

@@ -27,7 +27,7 @@ pub fn run(app_state: &mut AppState) -> Result<ExitCode> {
stdout.write_all(b"Ran ")?; stdout.write_all(b"Ran ")?;
app_state app_state
.current_exercise() .current_exercise()
.terminal_file_link(&mut stdout)?; .terminal_file_link(&mut stdout, app_state.emit_file_links())?;
stdout.write_all(b" with errors\n")?; stdout.write_all(b" with errors\n")?;
return Ok(ExitCode::FAILURE); 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()? { if let Some(solution_path) = app_state.current_solution_path()? {
stdout.write_all(b"\n")?; 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")?; stdout.write_all(b"\n")?;
} }
@@ -50,7 +50,7 @@ pub fn run(app_state: &mut AppState) -> Result<ExitCode> {
stdout.write_all(b"Next exercise: ")?; stdout.write_all(b"Next exercise: ")?;
app_state app_state
.current_exercise() .current_exercise()
.terminal_file_link(&mut stdout)?; .terminal_file_link(&mut stdout, app_state.emit_file_links())?;
stdout.write_all(b"\n")?; stdout.write_all(b"\n")?;
} }
ExercisesProgress::AllDone => (), ExercisesProgress::AllDone => (),

View File

@@ -272,22 +272,18 @@ pub fn canonicalize(path: &str) -> Option<String> {
}) })
} }
pub fn terminal_file_link<'a>( pub fn file_path<'a, W: CountedWrite<'a>>(
writer: &mut impl CountedWrite<'a>, writer: &mut W,
path: &str,
canonical_path: &str,
color: Color, color: Color,
f: impl FnOnce(&mut W) -> io::Result<()>,
) -> io::Result<()> { ) -> io::Result<()> {
writer writer
.stdout() .stdout()
.queue(SetForegroundColor(color))? .queue(SetForegroundColor(color))?
.queue(SetAttribute(Attribute::Underlined))?; .queue(SetAttribute(Attribute::Underlined))?;
writer.stdout().write_all(b"\x1b]8;;file://")?;
writer.stdout().write_all(canonical_path.as_bytes())?; f(writer)?;
writer.stdout().write_all(b"\x1b\\")?;
// Only this part is visible.
writer.write_str(path)?;
writer.stdout().write_all(b"\x1b]8;;\x1b\\")?;
writer writer
.stdout() .stdout()
.queue(SetForegroundColor(Color::Reset))? .queue(SetForegroundColor(Color::Reset))?
@@ -296,6 +292,19 @@ pub fn terminal_file_link<'a>(
Ok(()) 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) { pub fn write_ansi(output: &mut Vec<u8>, command: impl Command) {
struct FmtWriter<'a>(&'a mut Vec<u8>); struct FmtWriter<'a>(&'a mut Vec<u8>);

View File

@@ -233,7 +233,7 @@ impl<'a> WatchState<'a> {
stdout.write_all(b"\n")?; stdout.write_all(b"\n")?;
if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status { 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( stdout.write_all(
@@ -252,7 +252,7 @@ impl<'a> WatchState<'a> {
stdout.write_all(b"\nCurrent exercise: ")?; stdout.write_all(b"\nCurrent exercise: ")?;
self.app_state self.app_state
.current_exercise() .current_exercise()
.terminal_file_link(stdout)?; .terminal_file_link(stdout, self.app_state.emit_file_links())?;
stdout.write_all(b"\n\n")?; stdout.write_all(b"\n\n")?;
self.show_prompt(stdout)?; self.show_prompt(stdout)?;