@@ -33,41 +33,115 @@ pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String {
3333 buffer
3434}
3535
36−pub fn replace_nonprintable(input: &str, tab_width: usize) -> String {
36+fn try_parse_utf8_char(input: &[u8]) -> Option<(char, usize)> {
37+ let str_from_utf8 = |seq| std::str::from_utf8(seq).ok();
38+
39+ let decoded = None
40+ .or(input.get(0..1).and_then(str_from_utf8).map(|c| (c, 1)))
41+ .or(input.get(0..2).and_then(str_from_utf8).map(|c| (c, 2)))
42+ .or(input.get(0..3).and_then(str_from_utf8).map(|c| (c, 3)))
43+ .or(input.get(0..4).and_then(str_from_utf8).map(|c| (c, 4)));
44+
45+ let decoded_char = decoded.map(|(seq, n)| (seq.chars().next().unwrap(), n));
46+
47+ decoded_char
48+}
49+
50+pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String {
3751 let mut output = String::new();
3852
3953 let tab_width = if tab_width == 0 { 4 } else { tab_width };
4054
41− for chr in input.chars() {
42− match chr {
43− // space
44− ' ' => output.push('•'),
45− // tab
46− '\t' => {
47− if tab_width == 1 {
48− output.push('↹');
49− } else {
50− output.push('├');
51− output.push_str(&"─".repeat(tab_width - 2));
52− output.push('┤');
55+ let mut idx = 0;
56+ let len = input.len();
57+ while idx < len {
58+ if let Some((chr, skip_ahead)) = try_parse_utf8_char(&input[idx..]) {
59+ idx += skip_ahead;
60+
61+ match chr {
62+ // space
63+ ' ' => output.push('•'),
64+ // tab
65+ '\t' => {
66+ if tab_width == 1 {
67+ output.push('↹');
68+ } else {
69+ output.push('├');
70+ output.push_str(&"─".repeat(tab_width - 2));
71+ output.push('┤');
72+ }
73+ }
74+ // line feed
75+ '\x0A' => output.push('␊'),
76+ // carriage return
77+ '\x0D' => output.push('␍'),
78+ // null
79+ '\x00' => output.push('␀'),
80+ // bell
81+ '\x07' => output.push('␇'),
82+ // backspace
83+ '\x08' => output.push('␈'),
84+ // escape
85+ '\x1B' => output.push('␛'),
86+ // printable ASCII
87+ c if c.is_ascii_alphanumeric()
88+ || c.is_ascii_punctuation()
89+ || c.is_ascii_graphic() =>
90+ {
91+ output.push(c)
5392 }
93+ // everything else
94+ c => output.push_str(&c.escape_unicode().collect::<String>()),
5495 }
55− // line feed
56− '\x0A' => output.push('␊'),
57− // carriage return
58− '\x0D' => output.push('␍'),
59− // null
60− '\x00' => output.push('␀'),
61− // bell
62− '\x07' => output.push('␇'),
63− // backspace
64− '\x08' => output.push('␈'),
65− // escape
66− '\x1B' => output.push('␛'),
67− // anything else
68− _ => output.push(chr),
96+ } else {
97+ output.push_str(&format!("\\x{:02X}", input[idx]));
98+ idx += 1;
6999 }
70100 }
71101
72102 output
73103}
104+
105+#[test]
106+fn test_try_parse_utf8_char() {
107+ assert_eq!(try_parse_utf8_char(&[0x20]), Some((' ', 1)));
108+ assert_eq!(try_parse_utf8_char(&[0x20, 0x20]), Some((' ', 1)));
109+ assert_eq!(try_parse_utf8_char(&[0x20, 0xef]), Some((' ', 1)));
110+
111+ assert_eq!(try_parse_utf8_char(&[0x00]), Some(('\x00', 1)));
112+ assert_eq!(try_parse_utf8_char(&[0x1b]), Some(('\x1b', 1)));
113+
114+ assert_eq!(try_parse_utf8_char(&[0xc3, 0xa4]), Some(('ä', 2)));
115+ assert_eq!(try_parse_utf8_char(&[0xc3, 0xa4, 0xef]), Some(('ä', 2)));
116+ assert_eq!(try_parse_utf8_char(&[0xc3, 0xa4, 0x20]), Some(('ä', 2)));
117+
118+ assert_eq!(try_parse_utf8_char(&[0xe2, 0x82, 0xac]), Some(('€', 3)));
119+ assert_eq!(
120+ try_parse_utf8_char(&[0xe2, 0x82, 0xac, 0xef]),
121+ Some(('€', 3))
122+ );
123+ assert_eq!(
124+ try_parse_utf8_char(&[0xe2, 0x82, 0xac, 0x20]),
125+ Some(('€', 3))
126+ );
127+
128+ assert_eq!(try_parse_utf8_char(&[0xe2, 0x88, 0xb0]), Some(('∰', 3)));
129+
130+ assert_eq!(
131+ try_parse_utf8_char(&[0xf0, 0x9f, 0x8c, 0x82]),
132+ Some(('🌂', 4))
133+ );
134+ assert_eq!(
135+ try_parse_utf8_char(&[0xf0, 0x9f, 0x8c, 0x82, 0xef]),
136+ Some(('🌂', 4))
137+ );
138+ assert_eq!(
139+ try_parse_utf8_char(&[0xf0, 0x9f, 0x8c, 0x82, 0x20]),
140+ Some(('🌂', 4))
141+ );
142+
143+ assert_eq!(try_parse_utf8_char(&[]), None);
144+ assert_eq!(try_parse_utf8_char(&[0xef]), None);
145+ assert_eq!(try_parse_utf8_char(&[0xef, 0x20]), None);
146+ assert_eq!(try_parse_utf8_char(&[0xf0, 0xf0]), None);
147+}