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]]
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",

View file

@ -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"

View file

@ -68,25 +68,25 @@ impl FilesSorter {
}
fn sort_naturally(&self, items: &mut Vec<File>) {
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<usize> = (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,
)
};

View file

@ -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<Text<'static>, 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<ratatui::style::Color> {
fn to_ansi_color(color: highlighting::Color) -> Option<ratatui::style::Color> {
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)
}
}