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)

This commit is contained in:
三咲雅 · Misaki Masa 2024-08-25 10:52:30 +08:00 committed by GitHub
parent 9cf85c09d2
commit 58ba459b63
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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)
}
}