fix(hyperlink): escape spaces in file path to make them work correctly

Some terminal emulator do it themselves, but we want to be correct everywhere
This commit is contained in:
ariasuni 2025-05-12 19:46:11 +02:00 committed by Christina Sørensen
parent 5bf6e3d06b
commit dc6075474f
2 changed files with 34 additions and 17 deletions

View file

@ -6,6 +6,7 @@
// SPDX-License-Identifier: MIT
use super::file_name::QuoteStyle;
use nu_ansi_term::{AnsiString as ANSIString, Style};
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
pub fn escape(
string: String,
@ -43,3 +44,34 @@ pub fn escape(
bits.push(quote_bit);
}
}
const HYPERLINK_ESCAPE_CHARS: &AsciiSet = &CONTROLS.add(b' ');
const HYPERLINK_OPENING_START: &str = "\x1B]8;;";
const HYPERLINK_OPENING_END: &str = "\x1B\x5C";
// Combination of both above tags
pub const HYPERLINK_CLOSING: &str = "\x1B]8;;\x1B\x5C";
pub fn get_hyperlink_start_tag(abs_path: &str) -> String {
let abs_path = utf8_percent_encode(abs_path, HYPERLINK_ESCAPE_CHARS).to_string();
// On Windows, `std::fs::canonicalize` adds the Win32 File prefix, which we need to remove
#[cfg(target_os = "windows")]
let abs_path = abs_path.strip_prefix("\\\\?\\").unwrap_or(&abs_path);
format!("{HYPERLINK_OPENING_START}file://{abs_path}{HYPERLINK_OPENING_END}")
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn hyperlink_start_tag_escape_spaces() {
assert_eq!(
get_hyperlink_start_tag("/folder name/file name").to_string(),
format!(
"{HYPERLINK_OPENING_START}file:///folder%20name/file%20name{HYPERLINK_OPENING_END}"
),
);
}
}

View file

@ -408,11 +408,6 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
&self,
style_override: Option<Style>,
) -> Vec<ANSIString<'unused>> {
use percent_encoding::{utf8_percent_encode, CONTROLS};
const HYPERLINK_START: &str = "\x1B]8;;";
const HYPERLINK_END: &str = "\x1B\x5C";
let file_style = style_override.unwrap_or(self.style());
let mut bits = Vec::new();
@ -423,15 +418,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
.absolute_path()
.and_then(|p| p.as_os_str().to_str())
{
let abs_path = utf8_percent_encode(abs_path, CONTROLS).to_string();
// On Windows, `std::fs::canonicalize` adds the Win32 File prefix, which we need to remove
#[cfg(target_os = "windows")]
let abs_path = abs_path.strip_prefix("\\\\?\\").unwrap_or(&abs_path);
bits.push(ANSIString::from(format!(
"{HYPERLINK_START}file://{abs_path}{HYPERLINK_END}"
)));
bits.push(ANSIString::from(escape::get_hyperlink_start_tag(abs_path)));
display_hyperlink = true;
}
@ -446,9 +433,7 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
);
if display_hyperlink {
bits.push(ANSIString::from(format!(
"{HYPERLINK_START}{HYPERLINK_END}"
)));
bits.push(ANSIString::from(escape::HYPERLINK_CLOSING));
}
bits