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:
@@ -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]| {
|
||||
|
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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()..])?;
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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 => (),
|
||||
|
29
src/term.rs
29
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>);
|
||||
|
||||
|
@@ -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)?;
|
||||
|
Reference in New Issue
Block a user