mirror of
https://github.com/uutils/coreutils
synced 2024-11-15 09:27:21 +00:00
Merge pull request #5603 from sylvestre/gnu-legacy
ls: Match the gnu behavior for colors
This commit is contained in:
commit
0308e015a6
3 changed files with 131 additions and 49 deletions
|
@ -289,7 +289,7 @@ indicatif = "0.17"
|
|||
itertools = "0.12.0"
|
||||
libc = "0.2.150"
|
||||
lscolors = { version = "0.16.0", default-features = false, features = [
|
||||
"nu-ansi-term",
|
||||
"gnu_legacy",
|
||||
] }
|
||||
memchr = "2"
|
||||
memmap2 = "0.9"
|
||||
|
|
|
@ -10,7 +10,8 @@ use clap::{
|
|||
crate_version, Arg, ArgAction, Command,
|
||||
};
|
||||
use glob::{MatchOptions, Pattern};
|
||||
use lscolors::LsColors;
|
||||
use lscolors::{LsColors, Style};
|
||||
|
||||
use number_prefix::NumberPrefix;
|
||||
use std::{cell::OnceCell, num::IntErrorKind};
|
||||
use std::{collections::HashSet, io::IsTerminal};
|
||||
|
@ -1900,6 +1901,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
|
|||
let mut dirs = Vec::<PathData>::new();
|
||||
let mut out = BufWriter::new(stdout());
|
||||
let mut dired = DiredOutput::default();
|
||||
let mut style_manager = StyleManager::new();
|
||||
let initial_locs_len = locs.len();
|
||||
|
||||
for loc in locs {
|
||||
|
@ -1933,7 +1935,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
|
|||
sort_entries(&mut files, config, &mut out);
|
||||
sort_entries(&mut dirs, config, &mut out);
|
||||
|
||||
display_items(&files, config, &mut out, &mut dired)?;
|
||||
display_items(&files, config, &mut out, &mut dired, &mut style_manager)?;
|
||||
|
||||
for (pos, path_data) in dirs.iter().enumerate() {
|
||||
// Do read_dir call here to match GNU semantics by printing
|
||||
|
@ -1985,6 +1987,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
|
|||
&mut out,
|
||||
&mut listed_ancestors,
|
||||
&mut dired,
|
||||
&mut style_manager,
|
||||
)?;
|
||||
}
|
||||
if config.dired {
|
||||
|
@ -2101,6 +2104,7 @@ fn enter_directory(
|
|||
out: &mut BufWriter<Stdout>,
|
||||
listed_ancestors: &mut HashSet<FileInformation>,
|
||||
dired: &mut DiredOutput,
|
||||
style_manager: &mut StyleManager,
|
||||
) -> UResult<()> {
|
||||
// Create vec of entries with initial dot files
|
||||
let mut entries: Vec<PathData> = if config.files == Files::All {
|
||||
|
@ -2153,7 +2157,7 @@ fn enter_directory(
|
|||
}
|
||||
}
|
||||
|
||||
display_items(&entries, config, out, dired)?;
|
||||
display_items(&entries, config, out, dired, style_manager)?;
|
||||
|
||||
if config.recursive {
|
||||
for e in entries
|
||||
|
@ -2194,7 +2198,15 @@ fn enter_directory(
|
|||
|
||||
show_dir_name(&e.p_buf, out);
|
||||
writeln!(out)?;
|
||||
enter_directory(e, rd, config, out, listed_ancestors, dired)?;
|
||||
enter_directory(
|
||||
e,
|
||||
rd,
|
||||
config,
|
||||
out,
|
||||
listed_ancestors,
|
||||
dired,
|
||||
style_manager,
|
||||
)?;
|
||||
listed_ancestors
|
||||
.remove(&FileInformation::from_path(&e.p_buf, e.must_dereference)?);
|
||||
} else {
|
||||
|
@ -2316,6 +2328,7 @@ fn display_items(
|
|||
config: &Config,
|
||||
out: &mut BufWriter<Stdout>,
|
||||
dired: &mut DiredOutput,
|
||||
style_manager: &mut StyleManager,
|
||||
) -> UResult<()> {
|
||||
// `-Z`, `--context`:
|
||||
// Display the SELinux security context or '?' if none is found. When used with the `-l`
|
||||
|
@ -2338,7 +2351,7 @@ fn display_items(
|
|||
display_additional_leading_info(item, &padding_collection, config, out)?;
|
||||
write!(out, "{more_info}")?;
|
||||
}
|
||||
display_item_long(item, &padding_collection, config, out, dired)?;
|
||||
display_item_long(item, &padding_collection, config, out, dired, style_manager)?;
|
||||
}
|
||||
} else {
|
||||
let mut longest_context_len = 1;
|
||||
|
@ -2358,7 +2371,7 @@ fn display_items(
|
|||
|
||||
for i in items {
|
||||
let more_info = display_additional_leading_info(i, &padding, config, out)?;
|
||||
let cell = display_file_name(i, config, prefix_context, more_info, out);
|
||||
let cell = display_file_name(i, config, prefix_context, more_info, out, style_manager);
|
||||
names_vec.push(cell);
|
||||
}
|
||||
|
||||
|
@ -2513,6 +2526,7 @@ fn display_item_long(
|
|||
config: &Config,
|
||||
out: &mut BufWriter<Stdout>,
|
||||
dired: &mut DiredOutput,
|
||||
style_manager: &mut StyleManager,
|
||||
) -> UResult<()> {
|
||||
let mut output_display: String = String::new();
|
||||
if config.dired {
|
||||
|
@ -2605,7 +2619,8 @@ fn display_item_long(
|
|||
|
||||
write!(output_display, " {} ", display_date(md, config)).unwrap();
|
||||
|
||||
let displayed_file = display_file_name(item, config, None, String::new(), out).contents;
|
||||
let displayed_file =
|
||||
display_file_name(item, config, None, String::new(), out, style_manager).contents;
|
||||
if config.dired {
|
||||
let (start, end) = dired::calculate_dired(
|
||||
&dired.dired_positions,
|
||||
|
@ -2687,7 +2702,8 @@ fn display_item_long(
|
|||
write!(output_display, " {}", pad_right("?", padding.uname)).unwrap();
|
||||
}
|
||||
|
||||
let displayed_file = display_file_name(item, config, None, String::new(), out).contents;
|
||||
let displayed_file =
|
||||
display_file_name(item, config, None, String::new(), out, style_manager).contents;
|
||||
let date_len = 12;
|
||||
|
||||
write!(
|
||||
|
@ -2985,6 +3001,7 @@ fn display_file_name(
|
|||
prefix_context: Option<usize>,
|
||||
more_info: String,
|
||||
out: &mut BufWriter<Stdout>,
|
||||
style_manager: &mut StyleManager,
|
||||
) -> Cell {
|
||||
// This is our return value. We start by `&path.display_name` and modify it along the way.
|
||||
let mut name = escape_name(&path.display_name, &config.quoting_style);
|
||||
|
@ -3008,13 +3025,14 @@ fn display_file_name(
|
|||
if let Some(ls_colors) = &config.color {
|
||||
let md = path.md(out);
|
||||
name = if md.is_some() {
|
||||
color_name(name, &path.p_buf, md, ls_colors)
|
||||
color_name(name, &path.p_buf, md, ls_colors, style_manager)
|
||||
} else {
|
||||
color_name(
|
||||
name,
|
||||
&path.p_buf,
|
||||
path.p_buf.symlink_metadata().ok().as_ref(),
|
||||
ls_colors,
|
||||
style_manager,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
@ -3103,6 +3121,7 @@ fn display_file_name(
|
|||
&target_data.p_buf,
|
||||
Some(&target_metadata),
|
||||
ls_colors,
|
||||
style_manager,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
|
@ -3137,11 +3156,50 @@ fn display_file_name(
|
|||
}
|
||||
}
|
||||
|
||||
fn color_name(name: String, path: &Path, md: Option<&Metadata>, ls_colors: &LsColors) -> String {
|
||||
match ls_colors.style_for_path_with_metadata(path, md) {
|
||||
Some(style) => {
|
||||
return style.to_nu_ansi_term_style().paint(name).to_string();
|
||||
/// We need this struct to be able to store the previous style.
|
||||
/// This because we need to check the previous value in case we don't need
|
||||
/// the reset
|
||||
struct StyleManager {
|
||||
current_style: Option<Style>,
|
||||
}
|
||||
|
||||
impl StyleManager {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
current_style: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_style(&mut self, new_style: &Style, name: &str) -> String {
|
||||
if let Some(current) = &self.current_style {
|
||||
if *current == *new_style {
|
||||
// Current style is the same as new style, apply without reset.
|
||||
let mut style = new_style.to_nu_ansi_term_style();
|
||||
style.prefix_with_reset = false;
|
||||
return style.paint(name).to_string();
|
||||
}
|
||||
}
|
||||
|
||||
// We are getting a new style, we need to reset it
|
||||
self.current_style = Some(new_style.clone());
|
||||
new_style
|
||||
.to_nu_ansi_term_style()
|
||||
.reset_before_style()
|
||||
.paint(name)
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Colors the provided name based on the style determined for the given path.
|
||||
fn color_name(
|
||||
name: String,
|
||||
path: &Path,
|
||||
md: Option<&Metadata>,
|
||||
ls_colors: &LsColors,
|
||||
style_manager: &mut StyleManager,
|
||||
) -> String {
|
||||
match ls_colors.style_for_path_with_metadata(path, md) {
|
||||
Some(style) => style_manager.apply_style(style, &name),
|
||||
None => name,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired tmpfs
|
||||
// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired tmpfs mdir
|
||||
|
||||
#[cfg(any(unix, feature = "feat_selinux"))]
|
||||
use crate::common::util::expected_result;
|
||||
|
@ -864,11 +864,11 @@ fn test_ls_zero() {
|
|||
.succeeds()
|
||||
.stdout_only("\"0-test-zero\"\x00\"2-test-zero\"\x00\"3-test-zero\"\x00");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["--zero", "--color=always"])
|
||||
.succeeds()
|
||||
.stdout_only("\x1b[1;34m0-test-zero\x1b[0m\x002-test-zero\x003-test-zero\x00");
|
||||
let result = scene.ucmd().args(&["--zero", "--color=always"]).succeeds();
|
||||
assert_eq!(
|
||||
result.stdout_str(),
|
||||
"\u{1b}[0m\u{1b}[01;34m0-test-zero\x1b[0m\x002-test-zero\x003-test-zero\x00"
|
||||
);
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
|
@ -921,12 +921,9 @@ fn test_ls_zero() {
|
|||
"\"0-test-zero\"\x00\"1\\ntest-zero\"\x00\"2-test-zero\"\x00\"3-test-zero\"\x00",
|
||||
);
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&["--zero", "--color=always"])
|
||||
.succeeds()
|
||||
.stdout_only(
|
||||
"\x1b[1;34m0-test-zero\x1b[0m\x001\ntest-zero\x002-test-zero\x003-test-zero\x00",
|
||||
let result = scene.ucmd().args(&["--zero", "--color=always"]).succeeds();
|
||||
assert_eq!(result.stdout_str(),
|
||||
"\u{1b}[0m\u{1b}[01;34m0-test-zero\x1b[0m\x001\ntest-zero\x002-test-zero\x003-test-zero\x00",
|
||||
);
|
||||
|
||||
scene
|
||||
|
@ -1202,12 +1199,21 @@ fn test_ls_long_symlink_color() {
|
|||
}
|
||||
|
||||
fn capture_colored_string(input: &str) -> (Color, Name) {
|
||||
let colored_name = Regex::new(r"\x1b\[([0-9;]+)m(.+)\x1b\[0m").unwrap();
|
||||
// Input can be:
|
||||
// \u{1b}[0m\u{1b}[01;36mln-dir3\u{1b}[0m
|
||||
// \u{1b}[0m\u{1b}[01;34m./dir1/dir2/dir3\u{1b}[0m
|
||||
// \u{1b}[0m\u{1b}[01;36mln-file-invalid\u{1b}[0m
|
||||
// \u{1b}[01;36mdir1/invalid-target\u{1b}[0m
|
||||
let colored_name = Regex::new(r"(?:\x1b\[0m\x1b)?\[([0-9;]+)m(.+)\x1b\[0m").unwrap();
|
||||
match colored_name.captures(input) {
|
||||
Some(captures) => (
|
||||
Some(captures) => {
|
||||
dbg!(captures.get(1).unwrap().as_str().to_string());
|
||||
dbg!(captures.get(2).unwrap().as_str().to_string());
|
||||
return (
|
||||
captures.get(1).unwrap().as_str().to_string(),
|
||||
captures.get(2).unwrap().as_str().to_string(),
|
||||
),
|
||||
);
|
||||
}
|
||||
None => (String::new(), input.to_string()),
|
||||
}
|
||||
}
|
||||
|
@ -1995,9 +2001,9 @@ fn test_ls_color() {
|
|||
at.touch(nested_file);
|
||||
at.touch("test-color");
|
||||
|
||||
let a_with_colors = "\x1b[1;34ma\x1b[0m";
|
||||
let z_with_colors = "\x1b[1;34mz\x1b[0m";
|
||||
let nested_dir_with_colors = "\x1b[1;34mnested_dir\x1b[0m"; // spell-checker:disable-line
|
||||
let a_with_colors = "\x1b[0m\x1b[01;34ma\x1b[0m";
|
||||
let z_with_colors = "\x1b[01;34mz\x1b[0m\n";
|
||||
let nested_dir_with_colors = "\x1b[0m\x1b[01;34mnested_dir\x1b[0m\x0anested_file"; // spell-checker:disable-line
|
||||
|
||||
// Color is disabled by default
|
||||
let result = scene.ucmd().succeeds();
|
||||
|
@ -2006,12 +2012,9 @@ fn test_ls_color() {
|
|||
|
||||
// Color should be enabled
|
||||
for param in ["--color", "--col", "--color=always", "--col=always"] {
|
||||
scene
|
||||
.ucmd()
|
||||
.arg(param)
|
||||
.succeeds()
|
||||
.stdout_contains(a_with_colors)
|
||||
.stdout_contains(z_with_colors);
|
||||
let result = scene.ucmd().arg(param).succeeds();
|
||||
assert!(result.stdout_str().contains(a_with_colors));
|
||||
assert!(result.stdout_str().contains(z_with_colors));
|
||||
}
|
||||
|
||||
// Color should be disabled
|
||||
|
@ -2020,12 +2023,8 @@ fn test_ls_color() {
|
|||
assert!(!result.stdout_str().contains(z_with_colors));
|
||||
|
||||
// Nested dir should be shown and colored
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--color")
|
||||
.arg("a")
|
||||
.succeeds()
|
||||
.stdout_contains(nested_dir_with_colors);
|
||||
let result = scene.ucmd().arg("--color").arg("a").succeeds();
|
||||
assert!(result.stdout_str().contains(nested_dir_with_colors));
|
||||
|
||||
// No output
|
||||
scene
|
||||
|
@ -2037,13 +2036,18 @@ fn test_ls_color() {
|
|||
|
||||
// The colors must not mess up the grid layout
|
||||
at.touch("b");
|
||||
scene
|
||||
let result = scene
|
||||
.ucmd()
|
||||
.arg("--color")
|
||||
.arg("-w=15")
|
||||
.arg("-C")
|
||||
.succeeds()
|
||||
.stdout_only(format!("{a_with_colors} test-color\nb {z_with_colors}\n"));
|
||||
.succeeds();
|
||||
let expected = format!("{} test-color\x0ab {}", a_with_colors, z_with_colors);
|
||||
assert_eq!(
|
||||
result.stdout_str().escape_default().to_string(),
|
||||
expected.escape_default().to_string()
|
||||
);
|
||||
assert_eq!(result.stdout_str(), expected);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
@ -3885,3 +3889,23 @@ fn test_ls_hyperlink() {
|
|||
.succeeds()
|
||||
.stdout_is(format!("{file}\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ls_color_do_not_reset() {
|
||||
let scene: TestScenario = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.mkdir("example");
|
||||
at.mkdir("example/a");
|
||||
at.mkdir("example/b");
|
||||
|
||||
let result = scene
|
||||
.ucmd()
|
||||
.arg("--color=always")
|
||||
.arg("example/")
|
||||
.succeeds();
|
||||
// the second color code should not have a reset
|
||||
assert_eq!(
|
||||
result.stdout_str().escape_default().to_string(),
|
||||
"\\u{1b}[0m\\u{1b}[01;34ma\\u{1b}[0m\\n\\u{1b}[01;34mb\\u{1b}[0m\\n"
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue