Skip to content

Commit 4ba2e17

Browse files
authored
Merge pull request eza-community#163 from eza-community/fix-file-escaping-and-hyperlink
fix: filename escaping (last character lost sometimes, no hyperlink)
2 parents f18c3c1 + df7d7eb commit 4ba2e17

File tree

4 files changed

+60
-59
lines changed

4 files changed

+60
-59
lines changed

Cargo.lock

+20-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ name = "eza"
3737

3838
[dependencies]
3939
ansiterm = "0.12.2"
40+
gethostname = "0.4.3"
4041
glob = "0.3"
4142
lazy_static = "1.3"
4243
libc = "0.2"
@@ -51,6 +52,7 @@ term_grid = "0.1"
5152
terminal_size = "0.2.6"
5253
timeago = { version = "0.4.1", default-features = false }
5354
unicode-width = "0.1"
55+
urlencoding = "2.1.3"
5456
zoneinfo_compiled = "0.5.1"
5557

5658
[dependencies.datetime]

src/output/escape.rs

+17-11
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,31 @@ use ansiterm::{ANSIString, Style};
22

33

44
pub fn escape(string: String, bits: &mut Vec<ANSIString<'_>>, good: Style, bad: Style) {
5-
if string.chars().all(|c| c >= 0x20 as char && c != 0x7f as char) {
5+
// if the string has no control character
6+
if string.chars().all(|c| !c.is_control()) {
67
bits.push(good.paint(string));
78
return;
89
}
910

11+
// the lengthier string of non control character can’t be bigger than the whole string
12+
let mut regular_char_buff = String::with_capacity(string.len());
1013
for c in string.chars() {
1114
// The `escape_default` method on `char` is *almost* what we want here, but
1215
// it still escapes non-ASCII UTF-8 characters, which are still printable.
1316

14-
if c >= 0x20 as char && c != 0x7f as char {
15-
// TODO: This allocates way too much,
16-
// hence the `all` check above.
17-
let mut s = String::new();
18-
s.push(c);
19-
bits.push(good.paint(s));
20-
}
21-
else {
22-
let s = c.escape_default().collect::<String>();
23-
bits.push(bad.paint(s));
17+
if c.is_control() {
18+
if !regular_char_buff.is_empty() {
19+
bits.push(good.paint(std::mem::take(&mut regular_char_buff)));
20+
}
21+
regular_char_buff.extend(c.escape_default());
22+
// biased towards regular characters, we push control characters immediately
23+
bits.push(bad.paint(std::mem::take(&mut regular_char_buff)));
24+
} else {
25+
regular_char_buff.push(c);
2426
}
2527
}
28+
// if last character was not a control character, the buffer is not empty!
29+
if !regular_char_buff.is_empty() {
30+
bits.push(good.paint(std::mem::take(&mut regular_char_buff)));
31+
}
2632
}

src/output/file_name.rs

+21-46
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use crate::output::escape;
1010
use crate::output::icons::{icon_for_file, iconify_style};
1111
use crate::output::render::FiletypeColours;
1212

13+
const HYPERLINK_START: &str = "\x1B]8;;";
14+
const HYPERLINK_END: &str = "\x1B\x5C";
1315

1416
/// Basically a file name factory.
1517
#[derive(Debug, Copy, Clone)]
@@ -346,59 +348,32 @@ impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
346348
let file_style = self.style();
347349
let mut bits = Vec::new();
348350

349-
self.escape_color_and_hyperlinks(
351+
let mut display_hyperlink = false;
352+
if self.options.embed_hyperlinks == EmbedHyperlinks::On {
353+
if let Some(abs_path) = self.file.path.canonicalize().unwrap().as_os_str().to_str() {
354+
bits.insert(0, ANSIString::from(format!(
355+
"{}file://{}{}{}",
356+
HYPERLINK_START,
357+
gethostname::gethostname().to_str().unwrap_or(""),
358+
urlencoding::encode(abs_path).replace("%2F", "/"),
359+
HYPERLINK_END,
360+
)));
361+
display_hyperlink = true;
362+
}
363+
}
364+
365+
escape(
366+
self.file.name.clone(),
350367
&mut bits,
351368
file_style,
352369
self.colours.control_char(),
353370
);
354371

355-
bits
356-
}
357-
358-
// An adapted version of escape::escape.
359-
// afaik of all the calls to escape::escape, only for escaped_file_name, the call to escape needs to be checked for hyper links
360-
// and if that's the case then I think it's best to not try and generalize escape::escape to this case,
361-
// as this adaptation would incur some unneeded operations there
362-
pub fn escape_color_and_hyperlinks(&self, bits: &mut Vec<ANSIString<'_>>, good: Style, bad: Style) {
363-
let string = self.file.name.clone();
364-
365-
if string.chars().all(|c| c >= 0x20 as char && c != 0x7f as char) {
366-
let painted = good.paint(string);
367-
368-
let adjusted_filename = if let EmbedHyperlinks::On = self.options.embed_hyperlinks {
369-
ANSIString::from(format!("\x1B]8;;{}\x1B\x5C{}\x1B]8;;\x1B\x5C", self.file.path.display(), painted))
370-
} else {
371-
painted
372-
};
373-
bits.push(adjusted_filename);
374-
return;
372+
if display_hyperlink {
373+
bits.push(ANSIString::from(format!("{HYPERLINK_START}{HYPERLINK_END}")));
375374
}
376375

377-
// again adapted from escape::escape
378-
// still a slow route, but slightly improved to at least not reallocate buff + have a predetermined buff size
379-
//
380-
// also note that buff would never need more than len,
381-
// even tho 'in total' it will be lenghier than len (as we expand with escape_default),
382-
// because we clear it after an irregularity
383-
let mut buff = String::with_capacity(string.len());
384-
for c in string.chars() {
385-
// The `escape_default` method on `char` is *almost* what we want here, but
386-
// it still escapes non-ASCII UTF-8 characters, which are still printable.
387-
388-
if c >= 0x20 as char && c != 0x7f as char {
389-
buff.push(c);
390-
}
391-
else {
392-
if ! buff.is_empty() {
393-
bits.push(good.paint(std::mem::take(&mut buff)));
394-
}
395-
// biased towards regular characters, so we still collect on first sight of bad char
396-
for e in c.escape_default() {
397-
buff.push(e);
398-
}
399-
bits.push(bad.paint(std::mem::take(&mut buff)));
400-
}
401-
}
376+
bits
402377
}
403378

404379
/// Figures out which colour to paint the filename part of the output,

0 commit comments

Comments
 (0)