Skip to content

Commit 6c7c238

Browse files
committed
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 sharkdp#720.
1 parent a5f17db commit 6c7c238

File tree

1 file changed

+38
-18
lines changed

1 file changed

+38
-18
lines changed

src/output.rs

+38-18
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
use std::borrow::Cow;
12
use std::io::{self, StdoutLock, Write};
23
use std::path::Path;
34
use std::process;
45
use std::sync::atomic::{AtomicBool, Ordering};
56
use std::sync::Arc;
67

7-
use lscolors::{LsColors, Style};
8+
use lscolors::{Indicator, LsColors, Style};
89

910
use crate::error::print_error;
1011
use crate::exit_codes::ExitCode;
@@ -53,32 +54,51 @@ fn print_entry_colorized(
5354
ls_colors: &LsColors,
5455
wants_to_quit: &Arc<AtomicBool>,
5556
) -> io::Result<()> {
56-
let default_style = ansi_term::Style::default();
57-
58-
// Traverse the path and colorize each component
59-
for (component, style) in ls_colors.style_for_path_components(path) {
60-
let style = style
61-
.map(Style::to_ansi_term_style)
62-
.unwrap_or(default_style);
57+
// Split the path between the parent and the last component
58+
let mut offset = 0;
59+
let path_str = path.to_string_lossy();
60+
61+
if let Some(parent) = path.parent() {
62+
offset = parent.to_string_lossy().len();
63+
for c in path_str[offset..].chars() {
64+
if std::path::is_separator(c) {
65+
offset += c.len_utf8();
66+
} else {
67+
break;
68+
}
69+
}
70+
}
6371

64-
let mut path_string = component.to_string_lossy();
72+
if offset > 0 {
73+
let mut parent_str = Cow::from(&path_str[..offset]);
6574
if let Some(ref separator) = config.path_separator {
66-
*path_string.to_mut() = replace_path_separator(&path_string, separator);
75+
*parent_str.to_mut() = replace_path_separator(&parent_str, separator);
6776
}
68-
write!(stdout, "{}", style.paint(path_string))?;
6977

70-
// TODO: can we move this out of the if-statement? Why do we call it that often?
71-
if wants_to_quit.load(Ordering::Relaxed) {
72-
writeln!(stdout)?;
73-
process::exit(ExitCode::KilledBySigint.into());
74-
}
78+
let style = ls_colors
79+
.style_for_indicator(Indicator::Directory)
80+
.map(Style::to_ansi_term_style)
81+
.unwrap_or_default();
82+
write!(stdout, "{}", style.paint(parent_str))?;
7583
}
7684

85+
let style = ls_colors
86+
.style_for_path(path)
87+
.map(Style::to_ansi_term_style)
88+
.unwrap_or_default();
89+
write!(stdout, "{}", style.paint(&path_str[offset..]))?;
90+
7791
if config.null_separator {
78-
write!(stdout, "\0")
92+
write!(stdout, "\0")?;
7993
} else {
80-
writeln!(stdout)
94+
writeln!(stdout)?;
8195
}
96+
97+
if wants_to_quit.load(Ordering::Relaxed) {
98+
process::exit(ExitCode::KilledBySigint.into());
99+
}
100+
101+
Ok(())
82102
}
83103

84104
// TODO: this function is performance critical and can probably be optimized

0 commit comments

Comments
 (0)