Repository: sxyazi/yazi (language: rust)

Issue:
Extra symbols remained in the window after trying to preview ASCII escape codes
### What system are you running Yazi on?

macOS

### What terminal are you running Yazi in?

kitty 0.35.2

### `yazi --debug` output

<details>
Yazi
    Version: 0.3.0 (Homebrew 2024-08-01)
    Debug  : false
    OS     : macos-x86_64 (unix)

Ya
    Version: 0.3.0

Emulator
    Emulator.via_env: ("xterm-kitty", "")
    Emulator.via_csi: Ok(Kitty)
    Emulator.detect : Kitty

Adapter
    Adapter.matches: Kitty

Desktop
    XDG_SESSION_TYPE: None
    WAYLAND_DISPLAY : None
    DISPLAY         : Some("/private/tmp/com.apple.launchd.RYRSLZAOnF/org.macosforge.xquartz:0")

SSH
    shared.in_ssh_connection: false

WSL
    /proc/sys/fs/binfmt_misc/WSLInterop: false

Variables
    SHELL              : Some("/bin/zsh")
    EDITOR             : Some("nvim")
    YAZI_FILE_ONE      : None
    YAZI_CONFIG_HOME   : None
    ZELLIJ_SESSION_NAME: None

Text Opener
    default: Some(Opener { run: "nvim \"$@\"", block: true, orphan: false, desc: "nvim", for_: None, spread: true })
    block  : Some(Opener { run: "nvim \"$@\"", block: true, orphan: false, desc: "nvim", for_: None, spread: true })

tmux
    TMUX   : false
    Version: No such file or directory (os error 2)

Dependencies
    file             : 5.41
    ueberzugpp       : No such file or directory (os error 2)
    ffmpegthumbnailer: No such file or directory (os error 2)
    magick           : No such file or directory (os error 2)
    fzf              : 0.54.2
    fd               : 10.1.0
    rg               : 14.1.0
    chafa            : No such file or directory (os error 2)
    zoxide           : 0.9.4
    7z               : No such file or directory (os error 2)
    7zz              : No such file or directory (os error 2)
    jq               : No such file or directory (os error 2)
</details>

### Did you try the latest nightly build to see if the problem got fixed?

Yes, and I updated the debug information above (`yazi --debug`) to the nightly that I tried

### Describe the bug

https://github.com/user-attachments/assets/2c7f0e86-4889-4152-8778-071547f026c1

### Expected Behavior

No extra symbols in the terminal window

### To Reproduce

1. `git clone https://github.com/nvim-telescope/telescope.nvim`
2. go to `./telescope.nvim/data/memes/planets`
3. hover one of the `planets/` files

### Configuration

_No response_

### Anything else?

1) If I exit yazi and open it again it resets the window.
2) I can "preview" the files using `cat`
<img width="835" alt="Screenshot 2024-08-01 at 22 26 00" src="https://github.com/user-attachments/assets/03ed4140-0b07-4c3c-bfef-277e3da708d8">


Discussion / hints:

Fix patch:
diff --git a/yazi-adapter/src/emulator.rs b/yazi-adapter/src/emulator.rs
index 73908dc8f..9e4495277 100644
--- a/yazi-adapter/src/emulator.rs
+++ b/yazi-adapter/src/emulator.rs
@@ -199,10 +199,10 @@ impl Emulator {
 					bail!("unexpected EOF");
 				}
 				buf.push(c[0]);
-				if c[0] != b'c' || !buf.contains(&b'\x1b') {
+				if c[0] != b'c' || !buf.contains(&0x1b) {
 					continue;
 				}
-				if buf.rsplitn(2, |&b| b == b'\x1b').next().is_some_and(|s| s.starts_with(b"[?")) {
+				if buf.rsplitn(2, |&b| b == 0x1b).next().is_some_and(|s| s.starts_with(b"[?")) {
 					break;
 				}
 			}
diff --git a/yazi-fm/src/input/input.rs b/yazi-fm/src/input/input.rs
index f4ad39cf4..c4b7cf8c7 100644
--- a/yazi-fm/src/input/input.rs
+++ b/yazi-fm/src/input/input.rs
@@ -3,7 +3,7 @@ use std::ops::Range;
 use anyhow::{bail, Result};
 use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::{Block, BorderType, Paragraph, Widget}};
 use syntect::easy::HighlightLines;
-use yazi_config::THEME;
+use yazi_config::{PREVIEW, THEME};
 use yazi_core::input::InputMode;
 use yazi_plugin::external::Highlighter;
 
@@ -21,11 +21,11 @@ impl<'a> Input<'a> {
 			bail!("Highlighting is disabled");
 		}
 
-		let (theme, syntaxes) = Highlighter::init();
+		let (theme, syntaxes) = futures::executor::block_on(Highlighter::init());
 		if let Some(syntax) = syntaxes.find_syntax_by_name("Bourne Again Shell (bash)") {
 			let mut h = HighlightLines::new(syntax, theme);
 			let regions = h.highlight_line(self.cx.input.value(), syntaxes)?;
-			return Ok(Highlighter::to_line_widget(regions));
+			return Ok(Highlighter::to_line_widget(regions, &" ".repeat(PREVIEW.tab_size as usize)));
 		}
 		bail!("Failed to find syntax")
 	}
diff --git a/yazi-plugin/src/external/highlighter.rs b/yazi-plugin/src/external/highlighter.rs
index 0e681b960..3def21ff8 100644
--- a/yazi-plugin/src/external/highlighter.rs
+++ b/yazi-plugin/src/external/highlighter.rs
@@ -1,15 +1,14 @@
-use std::{io::Cursor, mem, path::{Path, PathBuf}, sync::{atomic::{AtomicUsize, Ordering}, OnceLock}};
+use std::{io::Cursor, mem, path::{Path, PathBuf}, sync::atomic::{AtomicUsize, Ordering}};
 
 use anyhow::{anyhow, Result};
 use ratatui::text::{Line, Span, Text};
-use syntect::{dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}};
-use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}};
+use syntect::{dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}, LoadingError};
+use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}, sync::OnceCell};
 use yazi_config::{PREVIEW, THEME};
 use yazi_shared::PeekError;
 
 static INCR: AtomicUsize = AtomicUsize::new(0);
-static SYNTECT_SYNTAX: OnceLock<SyntaxSet> = OnceLock::new();
-static SYNTECT_THEME: OnceLock<Theme> = OnceLock::new();
+static SYNTECT: OnceCell<(Theme, SyntaxSet)> = OnceCell::const_new();
 
 pub struct Highlighter {
 	path: PathBuf,
@@ -19,27 +18,28 @@ impl Highlighter {
 	#[inline]
 	pub fn new(path: &Path) -> Self { Self { path: path.to_owned() } }
 
-	pub fn init() -> (&'static Theme, &'static SyntaxSet) {
-		#[inline]
-		fn from_file() -> Result<Theme> {
-			let file = std::fs::File::open(&THEME.manager.syntect_theme)?;
-			Ok(ThemeSet::load_from_reader(&mut std::io::BufReader::new(file))?)
-		}
+	pub async fn init() -> (&'static Theme, &'static SyntaxSet) {
+		let fut = async {
+			tokio::task::spawn_blocking(|| {
+				let theme = std::fs::File::open(&THEME.manager.syntect_theme)
+					.map_err(LoadingError::Io)
+					.and_then(|f| ThemeSet::load_from_reader(&mut std::io::BufReader::new(f)))
+					.or_else(|_| ThemeSet::load_from_reader(&mut Cursor::new(yazi_prebuild::ansi_theme())));
 
-		let theme = SYNTECT_THEME.get_or_init(|| {
-			from_file().unwrap_or_else(|_| {
-				ThemeSet::load_from_reader(&mut Cursor::new(yazi_prebuild::ansi_theme())).unwrap()
-			})
-		});
+				let syntaxes = dumps::from_uncompressed_data(yazi_prebuild::syntaxes());
 
-		let syntaxes = SYNTECT_SYNTAX
-			.get_or_init(|| dumps::from_uncompressed_data(yazi_prebuild::syntaxes()).unwrap());
+				(theme.unwrap(), syntaxes.unwrap())
+			})
+			.await
+			.unwrap()
+		};
 
-		(theme, syntaxes)
+		let r = SYNTECT.get_or_init(|| fut).await;
+		(&r.0, &r.1)
 	}
 
 	async fn find_syntax(path: &Path) -> Result<&'static SyntaxReference> {
-		let (_, syntaxes) = Self::init();
+		let (_, syntaxes) = Self::init().await;
 		let name = path.file_name().map(|n| n.to_string_lossy()).unwrap_or_default();
 		if let Some(s) = syntaxes.find_syntax_by_extension(&name) {
 			return Ok(s);
@@ -73,7 +73,7 @@ impl Highlighter {
 				break;
 			}
 
-			if !plain && buf.len() > 6000 {
+			if !plain && (buf.len() > 5000 || buf.contains(&0x1b)) {
 				plain = true;
 				drop(mem::take(&mut before));
 			}
@@ -98,7 +98,7 @@ impl Highlighter {
 
 		if plain {
 			let indent = " ".repeat(PREVIEW.tab_size as usize);
-			Ok(Text::from(after.join("").replace('\t', &indent)))
+			Ok(Text::from(after.join("").replace('\x1b', "^[").replace('\t', &indent)))
 		} else {
 			Self::highlight_with(before, after, syntax.unwrap()).await
 		}
@@ -110,11 +110,10 @@ impl Highlighter {
 		syntax: &'static SyntaxReference,
 	) -> Result<Text<'static>, PeekError> {
 		let ticket = INCR.load(Ordering::Relaxed);
+		let (theme, syntaxes) = Self::init().await;
 
 		tokio::task::spawn_blocking(move || {
-			let (theme, syntaxes) = Self::init();
 			let mut h = HighlightLines::new(syntax, theme);
-
 			for line in before {
 				if ticket != INCR.load(Ordering::Relaxed) {
 					return Err("Highlighting cancelled".into());
@@ -122,6 +121,7 @@ impl Highlighter {
 				h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?;
 			}
 
+			let indent = " ".repeat(PREVIEW.tab_size as usize);
 			let mut lines = Vec::with_capacity(after.len());
 			for line in after {
 				if ticket != INCR.load(Ordering::Relaxed) {
@@ -129,7 +129,7 @@ impl Highlighter {
 				}
 
 				let regions = h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?;
-				lines.push(Self::to_line_widget(regions));
+				lines.push(Self::to_line_widget(regions, &indent));
 			}
 
 			Ok(Text::from(lines))
@@ -182,8 +182,7 @@ impl Highlighter {
 		}
 	}
 
-	pub fn to_line_widget(regions: Vec<(highlighting::Style, &str)>) -> Line<'static> {
-		let indent = " ".repeat(PREVIEW.tab_size as usize);
+	pub fn to_line_widget(regions: Vec<(highlighting::Style, &str)>, indent: &str) -> Line<'static> {
 		let spans: Vec<_> = regions
 			.into_iter()
 			.map(|(style, s)| {
@@ -199,7 +198,7 @@ impl Highlighter {
 				}
 
 				Span {
-					content: s.replace('\t', &indent).into(),
+					content: s.replace('\t', indent).into(),
 					style:   ratatui::style::Style {
 						fg: Self::to_ansi_color(style.foreground),
 						// bg: Self::to_ansi_color(style.background),


Test patch: