Don't color each path component separately

It can be expensive to color each path component separately, requiring a
stat() call on each component.  For deep hierarchies this can result in
quadratic overhead.  Instead, just color the path up to the basename as
a directory.

Fixes #720.
This commit is contained in:
Tavian Barnes 2021-09-16 15:20:12 -04:00 committed by David Peter
parent 21fd013073
commit 66e3ccc5e1

View file

@ -1,10 +1,11 @@
use std::borrow::Cow;
use std::io::{self, StdoutLock, Write}; use std::io::{self, StdoutLock, Write};
use std::path::Path; use std::path::Path;
use std::process; use std::process;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use lscolors::{LsColors, Style}; use lscolors::{Indicator, LsColors, Style};
use crate::config::Config; use crate::config::Config;
use crate::error::print_error; use crate::error::print_error;
@ -53,32 +54,51 @@ fn print_entry_colorized(
ls_colors: &LsColors, ls_colors: &LsColors,
wants_to_quit: &Arc<AtomicBool>, wants_to_quit: &Arc<AtomicBool>,
) -> io::Result<()> { ) -> io::Result<()> {
let default_style = ansi_term::Style::default(); // Split the path between the parent and the last component
let mut offset = 0;
let path_str = path.to_string_lossy();
// Traverse the path and colorize each component if let Some(parent) = path.parent() {
for (component, style) in ls_colors.style_for_path_components(path) { offset = parent.to_string_lossy().len();
let style = style for c in path_str[offset..].chars() {
.map(Style::to_ansi_term_style) if std::path::is_separator(c) {
.unwrap_or(default_style); offset += c.len_utf8();
} else {
let mut path_string = component.to_string_lossy(); break;
if let Some(ref separator) = config.path_separator { }
*path_string.to_mut() = replace_path_separator(&path_string, separator);
}
write!(stdout, "{}", style.paint(path_string))?;
// TODO: can we move this out of the if-statement? Why do we call it that often?
if wants_to_quit.load(Ordering::Relaxed) {
writeln!(stdout)?;
process::exit(ExitCode::KilledBySigint.into());
} }
} }
if offset > 0 {
let mut parent_str = Cow::from(&path_str[..offset]);
if let Some(ref separator) = config.path_separator {
*parent_str.to_mut() = replace_path_separator(&parent_str, separator);
}
let style = ls_colors
.style_for_indicator(Indicator::Directory)
.map(Style::to_ansi_term_style)
.unwrap_or_default();
write!(stdout, "{}", style.paint(parent_str))?;
}
let style = ls_colors
.style_for_path(path)
.map(Style::to_ansi_term_style)
.unwrap_or_default();
write!(stdout, "{}", style.paint(&path_str[offset..]))?;
if config.null_separator { if config.null_separator {
write!(stdout, "\0") write!(stdout, "\0")?;
} else { } else {
writeln!(stdout) writeln!(stdout)?;
} }
if wants_to_quit.load(Ordering::Relaxed) {
process::exit(ExitCode::KilledBySigint.into());
}
Ok(())
} }
// TODO: this function is performance critical and can probably be optimized // TODO: this function is performance critical and can probably be optimized