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

This commit is contained in:
sxyazi 2024-08-25 10:46:40 +08:00
parent 9cf85c09d2
commit 6ec7a7e3cd
No known key found for this signature in database
4 changed files with 91 additions and 78 deletions

12
Cargo.lock generated
View file

@ -2131,9 +2131,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.208" version = "1.0.209"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -2150,9 +2150,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.208" version = "1.0.209"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2161,9 +2161,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.125" version = "1.0.127"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",

View file

@ -27,8 +27,8 @@ parking_lot = "0.12.3"
ratatui = { version = "0.27.0", features = [ "unstable-rendered-line-info" ] } ratatui = { version = "0.27.0", features = [ "unstable-rendered-line-info" ] }
regex = "1.10.6" regex = "1.10.6"
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = { version = "1.0.208", features = [ "derive" ] } serde = { version = "1.0.209", features = [ "derive" ] }
serde_json = "1.0.125" serde_json = "1.0.127"
shell-words = "1.1.0" shell-words = "1.1.0"
tokio = { version = "1.39.3", features = [ "full" ] } tokio = { version = "1.39.3", features = [ "full" ] }
tokio-stream = "0.1.15" tokio-stream = "0.1.15"

View file

@ -68,25 +68,25 @@ impl FilesSorter {
} }
fn sort_naturally(&self, items: &mut Vec<File>) { fn sort_naturally(&self, items: &mut Vec<File>) {
let mut indices = Vec::with_capacity(items.len()); let mut indices: Vec<usize> = (0..items.len()).collect();
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());
}
indices.sort_unstable_by(|&a, &b| { 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 { if promote != Ordering::Equal {
return promote; return promote;
} }
let ordering = if !self.translit { let ordering = if self.translit {
natsort(entities[a], entities[b], !self.sensitive) 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 { } else {
natsort( natsort(
entities[a].transliterate().as_bytes(), a.url.as_os_str().as_encoded_bytes(),
entities[b].transliterate().as_bytes(), b.url.as_os_str().as_encoded_bytes(),
!self.sensitive, !self.sensitive,
) )
}; };

View file

@ -38,23 +38,8 @@ impl Highlighter {
(&r.0, &r.1) (&r.0, &r.1)
} }
async fn find_syntax(path: &Path) -> Result<&'static SyntaxReference> { #[inline]
let (_, syntaxes) = Self::init().await; pub fn abort() { INCR.fetch_add(1, Ordering::Relaxed); }
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"))
}
pub async fn highlight(&self, skip: usize, limit: usize) -> Result<Text<'static>, PeekError> { pub async fn highlight(&self, skip: usize, limit: usize) -> Result<Text<'static>, PeekError> {
let mut reader = BufReader::new(File::open(&self.path).await?); let mut reader = BufReader::new(File::open(&self.path).await?);
@ -67,10 +52,13 @@ impl Highlighter {
let mut i = 0; let mut i = 0;
let mut buf = vec![]; let mut buf = vec![];
let mut inspected = 0u16;
while reader.read_until(b'\n', &mut buf).await.is_ok() { while reader.read_until(b'\n', &mut buf).await.is_ok() {
i += 1; i += 1;
if buf.is_empty() || i > skip + limit { if buf.is_empty() || i > skip + limit {
break; break;
} else if Self::is_binary(&buf, &mut inspected) {
return Err("Binary file".into());
} }
if !plain && (buf.len() > 5000 || buf.contains(&0x1b)) { if !plain && (buf.len() > 5000 || buf.contains(&0x1b)) {
@ -84,15 +72,8 @@ impl Highlighter {
buf.push(b'\n'); 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 { if i > skip {
buf.iter_mut().for_each(Self::carriage_return_to_line_feed);
after.push(String::from_utf8_lossy(&buf).into_owned()); after.push(String::from_utf8_lossy(&buf).into_owned());
} else if !plain { } else if !plain {
before.push(String::from_utf8_lossy(&buf).into_owned()); before.push(String::from_utf8_lossy(&buf).into_owned());
@ -144,13 +125,75 @@ impl Highlighter {
.await? .await?
} }
#[inline] async fn find_syntax(path: &Path) -> Result<&'static SyntaxReference> {
pub fn abort() { INCR.fetch_add(1, Ordering::Relaxed); } 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 { 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 // Copy from https://github.com/sharkdp/bat/blob/master/src/terminal.rs
pub fn to_ansi_color(color: highlighting::Color) -> Option<ratatui::style::Color> { fn to_ansi_color(color: highlighting::Color) -> Option<ratatui::style::Color> {
if color.a == 0 { if color.a == 0 {
// Themes can specify one of the user-configurable terminal colors by // Themes can specify one of the user-configurable terminal colors by
// encoding them as #RRGGBBAA with AA set to 00 (transparent) and RR set // 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)) 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)
}
} }