Skip to content

Commit

Permalink
Don't color each path component separately
Browse files Browse the repository at this point in the history
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 sharkdp#720.
  • Loading branch information
tavianator authored and sharkdp committed Nov 14, 2021
1 parent 21fd013 commit 58ac77d
Showing 1 changed file with 38 additions and 18 deletions.
56 changes: 38 additions & 18 deletions src/output.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::borrow::Cow;
use std::io::{self, StdoutLock, Write};
use std::path::Path;
use std::process;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use lscolors::{LsColors, Style};
use lscolors::{Indicator, LsColors, Style};

use crate::config::Config;
use crate::error::print_error;
Expand Down Expand Up @@ -53,32 +54,51 @@ fn print_entry_colorized(
ls_colors: &LsColors,
wants_to_quit: &Arc<AtomicBool>,
) -> io::Result<()> {
let default_style = ansi_term::Style::default();

// Traverse the path and colorize each component
for (component, style) in ls_colors.style_for_path_components(path) {
let style = style
.map(Style::to_ansi_term_style)
.unwrap_or(default_style);
// Split the path between the parent and the last component
let mut offset = 0;
let path_str = path.to_string_lossy();

if let Some(parent) = path.parent() {
offset = parent.to_string_lossy().len();
for c in path_str[offset..].chars() {
if std::path::is_separator(c) {
offset += c.len_utf8();
} else {
break;
}
}
}

let mut path_string = component.to_string_lossy();
if offset > 0 {
let mut parent_str = Cow::from(&path_str[..offset]);
if let Some(ref separator) = config.path_separator {
*path_string.to_mut() = replace_path_separator(&path_string, separator);
*parent_str.to_mut() = replace_path_separator(&parent_str, 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());
}
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 {
write!(stdout, "\0")
write!(stdout, "\0")?;
} 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
Expand Down

0 comments on commit 58ac77d

Please sign in to comment.