diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 361e6f3dcce..5800c795b27 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -254,6 +254,5 @@ Hijri Nowruz charmap hijri - ctype clocale diff --git a/src/uu/ls/src/display.rs b/src/uu/ls/src/display.rs index b9d109ac0fa..b875462435b 100644 --- a/src/uu/ls/src/display.rs +++ b/src/uu/ls/src/display.rs @@ -184,6 +184,14 @@ fn locale_quote(name: &OsStr, style: LocaleQuoting) -> OsString { quoted.push_str("\\\\"); } else if c.is_ascii() && c.is_control() { push_basic_escape(&mut quoted, c as u8); + } else if c.is_control() { + // Non-ASCII control characters (the C1 range, e.g. + // U+0085 NEL) are not printable; octal-escape their + // UTF-8 bytes like GNU does for non-printable chars. + let mut buf = [0u8; 4]; + for &byte in c.encode_utf8(&mut buf).as_bytes() { + let _ = write!(quoted, "\\{byte:03o}"); + } } else { quoted.push(c); } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 53ec7984b71..8208653ec54 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -2794,6 +2794,7 @@ mod quoting { at.touch("it's"); at.touch("say \"hi\""); at.touch("tab\there"); + at.touch("nel\u{0085}here"); let out = ucmd .env("LC_ALL", "en_US.UTF-8") @@ -2820,6 +2821,11 @@ mod quoting { out.contains(&format!("{lq}tab\\there{rq}")), "{style}: tab should be escaped as \\t: {out:?}" ); + // Non-ASCII (C1) control characters are octal-escaped by byte. + assert!( + out.contains(&format!("{lq}nel\\302\\205here{rq}")), + "{style}: U+0085 should be octal-escaped: {out:?}" + ); } // In the C locale, locale uses ASCII single quotes and clocale uses