From 40211df2192e91a80fbdbab3f0e7e938ff914f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Mon, 30 Sep 2024 00:37:33 +0800 Subject: [PATCH] fix: replace control characters to printable characters in plain text preview (#1704) --- yazi-plugin/src/external/highlighter.rs | 17 ++++++++----- yazi-shared/src/chars.rs | 33 ++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/yazi-plugin/src/external/highlighter.rs b/yazi-plugin/src/external/highlighter.rs index 3f6831c38..6ecf3ebda 100644 --- a/yazi-plugin/src/external/highlighter.rs +++ b/yazi-plugin/src/external/highlighter.rs @@ -5,7 +5,7 @@ use ratatui::{layout::Rect, text::{Line, Span, Text}}; use syntect::{LoadingError, dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}}; use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}, sync::OnceCell}; use yazi_config::{PREVIEW, THEME, preview::PreviewWrap}; -use yazi_shared::PeekError; +use yazi_shared::{PeekError, replace_to_printable}; static INCR: AtomicUsize = AtomicUsize::new(0); static SYNTECT: OnceCell<(Theme, SyntaxSet)> = OnceCell::const_new(); @@ -58,7 +58,7 @@ impl Highlighter { return Err("Binary file".into()); } - if !plain && (buf.len() > 5000 || buf.contains(&0x1b)) { + if !plain && (buf.len() > 5000 || Self::contains_control_chars(&buf)) { plain = true; drop(mem::take(&mut before)); } @@ -93,7 +93,7 @@ impl Highlighter { } Ok(if plain { - Text::from(after.join("").replace('\x1b', "^[").replace('\t', &PREVIEW.indent())) + Text::from(replace_to_printable(&after.join(""), PREVIEW.tab_size)) } else { Self::highlight_with(before, after, syntax.unwrap()).await? }) @@ -186,9 +186,14 @@ impl Highlighter { } #[inline(always)] - fn carriage_return_to_line_feed(c: &mut u8) { - if *c == b'\r' { - *c = b'\n'; + fn contains_control_chars(buf: &[u8]) -> bool { + buf.iter().any(|&b| b.is_ascii_control() && !matches!(b, b'\t' | b'\n' | b'\r')) + } + + #[inline(always)] + fn carriage_return_to_line_feed(b: &mut u8) { + if *b == b'\r' { + *b = b'\n'; } } } diff --git a/yazi-shared/src/chars.rs b/yazi-shared/src/chars.rs index 33f55ea9c..5d0035e27 100644 --- a/yazi-shared/src/chars.rs +++ b/yazi-shared/src/chars.rs @@ -1,3 +1,5 @@ +use core::str; + pub const MIME_DIR: &str = "inode/directory"; #[derive(Clone, Copy, PartialEq, Eq)] @@ -20,11 +22,34 @@ impl CharKind { } pub fn strip_trailing_newline(mut s: String) -> String { - if s.ends_with('\n') { - s.pop(); - } - if s.ends_with('\r') { + while s.ends_with('\n') || s.ends_with('\r') { s.pop(); } s } + +pub fn replace_to_printable(s: &str, tab_size: u8) -> String { + let mut buf = Vec::new(); + buf.try_reserve_exact(s.len() | 15).unwrap_or_else(|_| panic!()); + + for &b in s.as_bytes() { + match b { + b'\n' => buf.push(b'\n'), + b'\t' => { + for _ in 0..tab_size { + buf.push(b' '); + } + } + b'\0'..=b'\x1F' => { + buf.push(b'^'); + buf.push(b + b'@'); + } + 0x7f => { + buf.push(b'^'); + buf.push(b'?'); + } + _ => buf.push(b), + } + } + unsafe { String::from_utf8_unchecked(buf) } +}