Skip to content

Commit

Permalink
Merge pull request #163 from eza-community/fix-file-escaping-and-hype…
Browse files Browse the repository at this point in the history
…rlink

fix: filename escaping (last character lost sometimes, no hyperlink)
  • Loading branch information
cafkafk authored Sep 9, 2023
2 parents f18c3c1 + df7d7eb commit 4ba2e17
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 59 deletions.
22 changes: 20 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ name = "eza"

[dependencies]
ansiterm = "0.12.2"
gethostname = "0.4.3"
glob = "0.3"
lazy_static = "1.3"
libc = "0.2"
Expand All @@ -51,6 +52,7 @@ term_grid = "0.1"
terminal_size = "0.2.6"
timeago = { version = "0.4.1", default-features = false }
unicode-width = "0.1"
urlencoding = "2.1.3"
zoneinfo_compiled = "0.5.1"

[dependencies.datetime]
Expand Down
28 changes: 17 additions & 11 deletions src/output/escape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,31 @@ use ansiterm::{ANSIString, Style};


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

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

if c >= 0x20 as char && c != 0x7f as char {
// TODO: This allocates way too much,
// hence the `all` check above.
let mut s = String::new();
s.push(c);
bits.push(good.paint(s));
}
else {
let s = c.escape_default().collect::<String>();
bits.push(bad.paint(s));
if c.is_control() {
if !regular_char_buff.is_empty() {
bits.push(good.paint(std::mem::take(&mut regular_char_buff)));
}
regular_char_buff.extend(c.escape_default());
// biased towards regular characters, we push control characters immediately
bits.push(bad.paint(std::mem::take(&mut regular_char_buff)));
} else {
regular_char_buff.push(c);
}
}
// if last character was not a control character, the buffer is not empty!
if !regular_char_buff.is_empty() {
bits.push(good.paint(std::mem::take(&mut regular_char_buff)));
}
}
67 changes: 21 additions & 46 deletions src/output/file_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::output::escape;
use crate::output::icons::{icon_for_file, iconify_style};
use crate::output::render::FiletypeColours;

const HYPERLINK_START: &str = "\x1B]8;;";
const HYPERLINK_END: &str = "\x1B\x5C";

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

self.escape_color_and_hyperlinks(
let mut display_hyperlink = false;
if self.options.embed_hyperlinks == EmbedHyperlinks::On {
if let Some(abs_path) = self.file.path.canonicalize().unwrap().as_os_str().to_str() {
bits.insert(0, ANSIString::from(format!(
"{}file://{}{}{}",
HYPERLINK_START,
gethostname::gethostname().to_str().unwrap_or(""),
urlencoding::encode(abs_path).replace("%2F", "/"),
HYPERLINK_END,
)));
display_hyperlink = true;
}
}

escape(
self.file.name.clone(),
&mut bits,
file_style,
self.colours.control_char(),
);

bits
}

// An adapted version of escape::escape.
// afaik of all the calls to escape::escape, only for escaped_file_name, the call to escape needs to be checked for hyper links
// and if that's the case then I think it's best to not try and generalize escape::escape to this case,
// as this adaptation would incur some unneeded operations there
pub fn escape_color_and_hyperlinks(&self, bits: &mut Vec<ANSIString<'_>>, good: Style, bad: Style) {
let string = self.file.name.clone();

if string.chars().all(|c| c >= 0x20 as char && c != 0x7f as char) {
let painted = good.paint(string);

let adjusted_filename = if let EmbedHyperlinks::On = self.options.embed_hyperlinks {
ANSIString::from(format!("\x1B]8;;{}\x1B\x5C{}\x1B]8;;\x1B\x5C", self.file.path.display(), painted))
} else {
painted
};
bits.push(adjusted_filename);
return;
if display_hyperlink {
bits.push(ANSIString::from(format!("{HYPERLINK_START}{HYPERLINK_END}")));
}

// again adapted from escape::escape
// still a slow route, but slightly improved to at least not reallocate buff + have a predetermined buff size
//
// also note that buff would never need more than len,
// even tho 'in total' it will be lenghier than len (as we expand with escape_default),
// because we clear it after an irregularity
let mut buff = String::with_capacity(string.len());
for c in string.chars() {
// The `escape_default` method on `char` is *almost* what we want here, but
// it still escapes non-ASCII UTF-8 characters, which are still printable.

if c >= 0x20 as char && c != 0x7f as char {
buff.push(c);
}
else {
if ! buff.is_empty() {
bits.push(good.paint(std::mem::take(&mut buff)));
}
// biased towards regular characters, so we still collect on first sight of bad char
for e in c.escape_default() {
buff.push(e);
}
bits.push(bad.paint(std::mem::take(&mut buff)));
}
}
bits
}

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

0 comments on commit 4ba2e17

Please sign in to comment.