From 58ba459b63a0ab004f210436b9abcc44974e28eb 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: Sun, 25 Aug 2024 10:52:30 +0800 Subject: [PATCH] perf: only scan the first 1024 bytes to detect if it's binary, apply `\r` fixes only to content within the visible range, avoid unnecessary allocations during natural sorting (#1551) --- Cargo.lock | 12 +-- Cargo.toml | 4 +- yazi-fs/src/sorter.rs | 24 ++--- yazi-plugin/src/external/highlighter.rs | 129 +++++++++++++----------- 4 files changed, 91 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e43caf50..c21be335 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2131,9 +2131,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] @@ -2150,9 +2150,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", @@ -2161,9 +2161,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ "itoa", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 7ffee044..4758cddf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,8 +27,8 @@ parking_lot = "0.12.3" ratatui = { version = "0.27.0", features = [ "unstable-rendered-line-info" ] } regex = "1.10.6" scopeguard = "1.2.0" -serde = { version = "1.0.208", features = [ "derive" ] } -serde_json = "1.0.125" +serde = { version = "1.0.209", features = [ "derive" ] } +serde_json = "1.0.127" shell-words = "1.1.0" tokio = { version = "1.39.3", features = [ "full" ] } tokio-stream = "0.1.15" diff --git a/yazi-fs/src/sorter.rs b/yazi-fs/src/sorter.rs index 244f848e..b3d5ab85 100644 --- a/yazi-fs/src/sorter.rs +++ b/yazi-fs/src/sorter.rs @@ -68,25 +68,25 @@ impl FilesSorter { } fn sort_naturally(&self, items: &mut Vec) { - let mut indices = Vec::with_capacity(items.len()); - let mut entities = Vec::with_capacity(items.len()); - for (i, file) in items.iter().enumerate() { - indices.push(i); - entities.push(file.url.as_os_str().as_encoded_bytes()); - } - + let mut indices: Vec = (0..items.len()).collect(); indices.sort_unstable_by(|&a, &b| { - let promote = self.promote(&items[a], &items[b]); + let (a, b) = (&items[a], &items[b]); + + let promote = self.promote(a, b); if promote != Ordering::Equal { return promote; } - let ordering = if !self.translit { - natsort(entities[a], entities[b], !self.sensitive) + let ordering = if self.translit { + natsort( + a.url.as_os_str().as_encoded_bytes().transliterate().as_bytes(), + b.url.as_os_str().as_encoded_bytes().transliterate().as_bytes(), + !self.sensitive, + ) } else { natsort( - entities[a].transliterate().as_bytes(), - entities[b].transliterate().as_bytes(), + a.url.as_os_str().as_encoded_bytes(), + b.url.as_os_str().as_encoded_bytes(), !self.sensitive, ) }; diff --git a/yazi-plugin/src/external/highlighter.rs b/yazi-plugin/src/external/highlighter.rs index a00f1fd4..04bae7b9 100644 --- a/yazi-plugin/src/external/highlighter.rs +++ b/yazi-plugin/src/external/highlighter.rs @@ -38,23 +38,8 @@ impl Highlighter { (&r.0, &r.1) } - async fn find_syntax(path: &Path) -> Result<&'static SyntaxReference> { - 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); - } - - let ext = path.extension().map(|e| e.to_string_lossy()).unwrap_or_default(); - if let Some(s) = syntaxes.find_syntax_by_extension(&ext) { - return Ok(s); - } - - let mut line = String::new(); - let mut reader = BufReader::new(File::open(&path).await?); - reader.read_line(&mut line).await?; - syntaxes.find_syntax_by_first_line(&line).ok_or_else(|| anyhow!("No syntax found")) - } + #[inline] + pub fn abort() { INCR.fetch_add(1, Ordering::Relaxed); } pub async fn highlight(&self, skip: usize, limit: usize) -> Result, PeekError> { let mut reader = BufReader::new(File::open(&self.path).await?); @@ -67,10 +52,13 @@ impl Highlighter { let mut i = 0; let mut buf = vec![]; + let mut inspected = 0u16; while reader.read_until(b'\n', &mut buf).await.is_ok() { i += 1; if buf.is_empty() || i > skip + limit { break; + } else if Self::is_binary(&buf, &mut inspected) { + return Err("Binary file".into()); } if !plain && (buf.len() > 5000 || buf.contains(&0x1b)) { @@ -84,15 +72,8 @@ impl Highlighter { buf.push(b'\n'); } - for b in &mut buf { - match *b { - b'\0' => return Err("Binary file".into()), - b'\r' => *b = b'\n', // '\r' occurs in the middle of a line - _ => {} - } - } - if i > skip { + buf.iter_mut().for_each(Self::carriage_return_to_line_feed); after.push(String::from_utf8_lossy(&buf).into_owned()); } else if !plain { before.push(String::from_utf8_lossy(&buf).into_owned()); @@ -144,13 +125,75 @@ impl Highlighter { .await? } - #[inline] - pub fn abort() { INCR.fetch_add(1, Ordering::Relaxed); } + async fn find_syntax(path: &Path) -> Result<&'static SyntaxReference> { + 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); + } + + let ext = path.extension().map(|e| e.to_string_lossy()).unwrap_or_default(); + if let Some(s) = syntaxes.find_syntax_by_extension(&ext) { + return Ok(s); + } + + let mut line = String::new(); + let mut reader = BufReader::new(File::open(&path).await?); + reader.read_line(&mut line).await?; + syntaxes.find_syntax_by_first_line(&line).ok_or_else(|| anyhow!("No syntax found")) + } + + #[inline(always)] + fn is_binary(buf: &[u8], inspected: &mut u16) -> bool { + if let Some(n) = 1024u16.checked_sub(*inspected) { + *inspected += n.min(buf.len() as u16); + buf.iter().take(n as usize).any(|&b| b == 0) + } else { + false + } + } + + #[inline(always)] + fn carriage_return_to_line_feed(c: &mut u8) { + if *c == b'\r' { + *c = b'\n'; + } + } } impl Highlighter { + pub fn to_line_widget(regions: Vec<(highlighting::Style, &str)>, indent: &str) -> Line<'static> { + let spans: Vec<_> = regions + .into_iter() + .map(|(style, s)| { + let mut modifier = ratatui::style::Modifier::empty(); + if style.font_style.contains(highlighting::FontStyle::BOLD) { + modifier |= ratatui::style::Modifier::BOLD; + } + if style.font_style.contains(highlighting::FontStyle::ITALIC) { + modifier |= ratatui::style::Modifier::ITALIC; + } + if style.font_style.contains(highlighting::FontStyle::UNDERLINE) { + modifier |= ratatui::style::Modifier::UNDERLINED; + } + + Span { + content: s.replace('\t', indent).into(), + style: ratatui::style::Style { + fg: Self::to_ansi_color(style.foreground), + // bg: Self::to_ansi_color(style.background), + add_modifier: modifier, + ..Default::default() + }, + } + }) + .collect(); + + Line::from(spans) + } + // Copy from https://github.com/sharkdp/bat/blob/master/src/terminal.rs - pub fn to_ansi_color(color: highlighting::Color) -> Option { + fn to_ansi_color(color: highlighting::Color) -> Option { if color.a == 0 { // Themes can specify one of the user-configurable terminal colors by // encoding them as #RRGGBBAA with AA set to 00 (transparent) and RR set @@ -188,34 +231,4 @@ impl Highlighter { Some(ratatui::style::Color::Rgb(color.r, color.g, color.b)) } } - - pub fn to_line_widget(regions: Vec<(highlighting::Style, &str)>, indent: &str) -> Line<'static> { - let spans: Vec<_> = regions - .into_iter() - .map(|(style, s)| { - let mut modifier = ratatui::style::Modifier::empty(); - if style.font_style.contains(highlighting::FontStyle::BOLD) { - modifier |= ratatui::style::Modifier::BOLD; - } - if style.font_style.contains(highlighting::FontStyle::ITALIC) { - modifier |= ratatui::style::Modifier::ITALIC; - } - if style.font_style.contains(highlighting::FontStyle::UNDERLINE) { - modifier |= ratatui::style::Modifier::UNDERLINED; - } - - Span { - content: s.replace('\t', indent).into(), - style: ratatui::style::Style { - fg: Self::to_ansi_color(style.foreground), - // bg: Self::to_ansi_color(style.background), - add_modifier: modifier, - ..Default::default() - }, - } - }) - .collect(); - - Line::from(spans) - } }