@@ -33,41 +33,115 @@ pub fn expand_tabs(line: &str, width: usize, cursor: &mut 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();
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)));
45+ let decoded_char = decoded.map(|(seq, n)| (seq.chars().next().unwrap(), n));
36−pub fn replace_nonprintable(input: &str, tab_width: usize) -> String {
50+pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String {
37 let mut output = String::new();
51 let mut output = String::new();
39 let tab_width = if tab_width == 0 { 4 } else { tab_width };
53 let tab_width = if tab_width == 0 { 4 } else { tab_width };
41− for chr in input.chars() {
55+ let mut idx = 0;
56+ let len = input.len();
58+ if let Some((chr, skip_ahead)) = try_parse_utf8_char(&input[idx..]) {
42− match chr {
61+ match chr {
44− ' ' => output.push('•'),
63+ ' ' => output.push('•'),
46− '\t' => {
65+ '\t' => {
47− if tab_width == 1 {
66+ if tab_width == 1 {
48− output.push('↹');
67+ output.push('↹');
50− output.push('├');
69+ output.push('├');
51− output.push_str(&"─".repeat(tab_width - 2));
70+ output.push_str(&"─".repeat(tab_width - 2));
52− output.push('┤');
71+ output.push('┤');
75+ '\x0A' => output.push('␊'),
77+ '\x0D' => output.push('␍'),
79+ '\x00' => output.push('␀'),
81+ '\x07' => output.push('␇'),
83+ '\x08' => output.push('␈'),
85+ '\x1B' => output.push('␛'),
87+ c if c.is_ascii_alphanumeric()
88+ || c.is_ascii_punctuation()
89+ || c.is_ascii_graphic() =>
94+ c => output.push_str(&c.escape_unicode().collect::<String>()),
56− '\x0A' => output.push('␊'),
58− '\x0D' => output.push('␍'),
60− '\x00' => output.push('␀'),
62− '\x07' => output.push('␇'),
64− '\x08' => output.push('␈'),
66− '\x1B' => output.push('␛'),
67− // anything else
96+ } else {
68− _ => output.push(chr),
97+ output.push_str(&format!("\\x{:02X}", input[idx]));
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)));
111+ assert_eq!(try_parse_utf8_char(&[0x00]), Some(('\x00', 1)));
112+ assert_eq!(try_parse_utf8_char(&[0x1b]), Some(('\x1b', 1)));
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)));
118+ assert_eq!(try_parse_utf8_char(&[0xe2, 0x82, 0xac]), Some(('€', 3)));
120+ try_parse_utf8_char(&[0xe2, 0x82, 0xac, 0xef]),
124+ try_parse_utf8_char(&[0xe2, 0x82, 0xac, 0x20]),
128+ assert_eq!(try_parse_utf8_char(&[0xe2, 0x88, 0xb0]), Some(('∰', 3)));
131+ try_parse_utf8_char(&[0xf0, 0x9f, 0x8c, 0x82]),
135+ try_parse_utf8_char(&[0xf0, 0x9f, 0x8c, 0x82, 0xef]),
139+ try_parse_utf8_char(&[0xf0, 0x9f, 0x8c, 0x82, 0x20]),
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);