From 7a04b30d5a20db59f17db2c53a06b793f66eeadb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Fri, 25 Aug 2023 01:48:30 +0200 Subject: [PATCH] fix: filename escaping (last character lost sometimes, no hyperlink) --- Cargo.lock | 26 +++++++++++++--- Cargo.toml | 2 ++ src/output/escape.rs | 28 ++++++++++------- src/output/file_name.rs | 67 +++++++++++++---------------------------- 4 files changed, 62 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8c1b6cc1..483d3f85b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,6 +84,7 @@ version = "0.11.0" dependencies = [ "ansiterm", "datetime", + "gethostname", "git2", "glob", "lazy_static", @@ -99,6 +100,7 @@ dependencies = [ "terminal_size", "timeago", "unicode-width", + "urlencoding", "uzers", "zoneinfo_compiled", ] @@ -113,6 +115,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets", +] + [[package]] name = "git2" version = "0.18.0" @@ -129,9 +141,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "hermit-abi" @@ -409,9 +421,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "syn" -version = "2.0.29" +version = "2.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" dependencies = [ "proc-macro2", "quote", @@ -500,6 +512,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "uzers" version = "0.11.2" diff --git a/Cargo.toml b/Cargo.toml index 7dc18aadc..6645aae35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" @@ -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] diff --git a/src/output/escape.rs b/src/output/escape.rs index 29cacd962..05d598a30 100644 --- a/src/output/escape.rs +++ b/src/output/escape.rs @@ -2,25 +2,31 @@ use ansiterm::{ANSIString, Style}; pub fn escape(string: String, bits: &mut Vec>, 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::(); - 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))); + } } diff --git a/src/output/file_name.rs b/src/output/file_name.rs index 717e3000e..9b8a277c2 100644 --- a/src/output/file_name.rs +++ b/src/output/file_name.rs @@ -9,6 +9,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)] @@ -303,59 +305,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>, 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,