diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 232250dcf..f4e347147 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -55,7 +55,7 @@ use uucore::libc::{dev_t, major, minor}; #[cfg(unix)] use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::line_ending::LineEnding; -use uucore::quoting_style::{escape_name, QuotingStyle}; +use uucore::quoting_style::{escape_dir_name, escape_name, QuotingStyle}; use uucore::{ display::Quotable, error::{set_exit_code, UError, UResult}, @@ -2036,14 +2036,27 @@ impl PathData { } } +/// Show the directory name in the case where several arguments are given to ls +/// or the recursive flag is passed. +/// +/// ```no-exec +/// $ ls -R +/// .: <- This is printed by this function +/// dir1 file1 file2 +/// +/// dir1: <- This as well +/// file11 +/// ``` fn show_dir_name(path_data: &PathData, out: &mut BufWriter, config: &Config) { - if config.hyperlink && !config.dired { - let name = escape_name(path_data.p_buf.as_os_str(), &config.quoting_style); - let hyperlink = create_hyperlink(&name, path_data); - write!(out, "{hyperlink}:").unwrap(); + let escaped_name = escape_dir_name(path_data.p_buf.as_os_str(), &config.quoting_style); + + let name = if config.hyperlink && !config.dired { + create_hyperlink(&escaped_name, path_data) } else { - write!(out, "{}:", path_data.p_buf.display()).unwrap(); - } + escaped_name + }; + + write!(out, "{name}:").unwrap(); } #[allow(clippy::cognitive_complexity)] @@ -2327,9 +2340,10 @@ fn enter_directory( for e in entries .iter() .skip(if config.files == Files::All { 2 } else { 0 }) - .filter(|p| p.ft.get().is_some()) - .filter(|p| p.ft.get().unwrap().is_some()) - .filter(|p| p.ft.get().unwrap().unwrap().is_dir()) + .filter(|p| { + p.ft.get() + .is_some_and(|o_ft| o_ft.is_some_and(|ft| ft.is_dir())) + }) { match fs::read_dir(&e.p_buf) { Err(err) => { diff --git a/src/uucore/src/lib/features/quoting_style.rs b/src/uucore/src/lib/features/quoting_style.rs index cb98050a8..b76ad75ce 100644 --- a/src/uucore/src/lib/features/quoting_style.rs +++ b/src/uucore/src/lib/features/quoting_style.rs @@ -123,7 +123,7 @@ impl EscapedChar { } } - fn new_c(c: char, quotes: Quotes) -> Self { + fn new_c(c: char, quotes: Quotes, dirname: bool) -> Self { use EscapeState::*; let init_state = match c { '\x07' => Backslash('a'), @@ -142,10 +142,11 @@ impl EscapedChar { Quotes::Double => Backslash('"'), _ => Char('"'), }, - ' ' => match quotes { + ' ' if !dirname => match quotes { Quotes::None => Backslash(' '), _ => Char(' '), }, + ':' if dirname => Backslash(':'), _ if c.is_ascii_control() => Octal(EscapeOctal::from(c)), _ => Char(c), }; @@ -285,7 +286,10 @@ fn shell_with_escape(name: &str, quotes: Quotes) -> (String, bool) { } /// Escape a name according to the given quoting style. -pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { +/// +/// This inner function provides an additional flag `dirname` which +/// is meant for ls' directory name display. +fn escape_name_inner(name: &OsStr, style: &QuotingStyle, dirname: bool) -> String { match style { QuotingStyle::Literal { show_control } => { if *show_control { @@ -301,7 +305,7 @@ pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { let escaped_str: String = name .to_string_lossy() .chars() - .flat_map(|c| EscapedChar::new_c(c, *quotes)) + .flat_map(|c| EscapedChar::new_c(c, *quotes, dirname)) .collect(); match quotes { @@ -316,7 +320,10 @@ pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { show_control, } => { let name = name.to_string_lossy(); - let (quotes, must_quote) = if name.contains(&['"', '`', '$', '\\'][..]) { + + // Take ':' in account only if we are quoting a dirname + let start_index = if dirname { 0 } else { 1 }; + let (quotes, must_quote) = if name.contains(&[':', '"', '`', '$', '\\'][start_index..]) { (Quotes::Single, true) } else if name.contains('\'') { (Quotes::Double, true) @@ -341,6 +348,18 @@ pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { } } +/// Escape a filename with respect to the given style. +pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { + escape_name_inner(name, style, false) +} + +/// Escape a directory name with respect to the given style. +/// This is mainly meant to be used for ls' directory name printing and is not +/// likely to be used elsewhere. +pub fn escape_dir_name(dir_name: &OsStr, style: &QuotingStyle) -> String { + escape_name_inner(dir_name, style, true) +} + impl fmt::Display for QuotingStyle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self {