1use std::fmt::Write;
8
9pub fn escape_display(text: &str) -> String {
14 let mut out = String::with_capacity(text.len());
15 for ch in text.chars() {
16 match ch {
17 '\n' => out.push_str("\\n"),
18 '\r' => out.push_str("\\r"),
19 '\t' => out.push_str("\\t"),
20 c if (c as u32) < 0x20 || c as u32 == 0x7f => {
21 let _ = write!(out, "\\x{:02x}", c as u32);
22 }
23 c if is_bidi_override(c) => {
24 let _ = write!(out, "\\u{{{:04x}}}", c as u32);
25 }
26 c => out.push(c),
27 }
28 }
29 out
30}
31
32pub fn display_path(path: &std::path::Path) -> String {
36 let text = path.to_string_lossy();
37 if std::path::MAIN_SEPARATOR == '\\' {
38 text.replace('\\', "/")
39 } else {
40 text.into_owned()
41 }
42}
43
44fn is_bidi_override(c: char) -> bool {
48 matches!(c as u32, 0x202a..=0x202e | 0x2066..=0x2069)
49}
50
51#[cfg(test)]
52mod tests {
53 #[test]
54 fn display_path_uses_forward_slashes() {
55 let path = std::path::Path::new("samples/.github/workflows/lint.yml");
56 assert_eq!(
57 super::display_path(path),
58 "samples/.github/workflows/lint.yml"
59 );
60 #[cfg(windows)]
61 assert_eq!(
62 super::display_path(std::path::Path::new("samples/.github/workflows\\lint.yml")),
63 "samples/.github/workflows/lint.yml"
64 );
65 }
66
67 use super::*;
68
69 #[test]
70 fn control_characters_and_bidi_overrides_become_visible() {
71 assert_eq!(escape_display("plain"), "plain");
72 assert_eq!(escape_display("a\nb"), "a\\nb");
73 assert_eq!(escape_display("a\rb"), "a\\rb");
74 assert_eq!(escape_display("a\tb"), "a\\tb");
75 assert_eq!(escape_display("a\x1bb"), "a\\x1bb");
76 assert_eq!(escape_display("a\x07b"), "a\\x07b");
77 assert_eq!(escape_display("a\x7fb"), "a\\x7fb");
78 assert_eq!(escape_display("a\0b"), "a\\x00b");
79 assert_eq!(escape_display("a\u{202e}b"), "a\\u{202e}b");
80 assert_eq!(escape_display("a\u{2066}b"), "a\\u{2066}b");
81 assert_eq!(escape_display("caf\u{e9}"), "caf\u{e9}");
82 }
83}