on v0.21.0 help sign in
bat/src/preprocessor.rs
154 lines 5.1 kB history raw
1use console::AnsiCodeIterator;
3/// Expand tabs like an ANSI-enabled expand(1).
4pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String {
5 let mut buffer = String::with_capacity(line.len() * 2);
7 for chunk in AnsiCodeIterator::new(line) {
8 match chunk {
9 (text, true) => buffer.push_str(text),
10 (mut text, false) => {
11 while let Some(index) = text.find('\t') {
12 // Add previous text.
13 if index > 0 {
14 *cursor += index;
15 buffer.push_str(&text[0..index]);
16 }
18 // Add tab.
19 let spaces = width - (*cursor % width);
20 *cursor += spaces;
21 buffer.push_str(&*" ".repeat(spaces));
23 // Next.
24 text = &text[index + 1..text.len()];
25 }
27 *cursor += text.len();
28 buffer.push_str(text);
29 }
30 }
31 }
33 buffer
36fn try_parse_utf8_char(input: &[u8]) -> Option<(char, usize)> {
37 let str_from_utf8 = |seq| std::str::from_utf8(seq).ok();
39 let decoded = input
40 .get(0..1)
41 .and_then(str_from_utf8)
42 .map(|c| (c, 1))
43 .or_else(|| input.get(0..2).and_then(str_from_utf8).map(|c| (c, 2)))
44 .or_else(|| input.get(0..3).and_then(str_from_utf8).map(|c| (c, 3)))
45 .or_else(|| input.get(0..4).and_then(str_from_utf8).map(|c| (c, 4)));
47 decoded.map(|(seq, n)| (seq.chars().next().unwrap(), n))
50pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String {
51 let mut output = String::new();
53 let tab_width = if tab_width == 0 { 4 } else { tab_width };
55 let mut idx = 0;
56 let mut line_idx = 0;
57 let len = input.len();
58 while idx < len {
59 if let Some((chr, skip_ahead)) = try_parse_utf8_char(&input[idx..]) {
60 idx += skip_ahead;
61 line_idx += 1;
63 match chr {
64 // space
65 ' ' => output.push('·'),
66 // tab
67 '\t' => {
68 let tab_stop = tab_width - ((line_idx - 1) % tab_width);
69 line_idx = 0;
70 if tab_stop == 1 {
71 output.push('↹');
72 } else {
73 output.push('├');
74 output.push_str(&"─".repeat(tab_stop - 2));
75 output.push('┤');
76 }
77 }
78 // line feed
79 '\x0A' => {
80 output.push_str("␊\x0A");
81 line_idx = 0;
82 }
83 // carriage return
84 '\x0D' => output.push('␍'),
85 // null
86 '\x00' => output.push('␀'),
87 // bell
88 '\x07' => output.push('␇'),
89 // backspace
90 '\x08' => output.push('␈'),
91 // escape
92 '\x1B' => output.push('␛'),
93 // printable ASCII
94 c if c.is_ascii_alphanumeric()
95 || c.is_ascii_punctuation()
96 || c.is_ascii_graphic() =>
97 {
98 output.push(c)
99 }
100 // everything else
101 c => output.push_str(&c.escape_unicode().collect::<String>()),
102 }
103 } else {
104 output.push_str(&format!("\\x{:02X}", input[idx]));
105 idx += 1;
106 }
107 }
109 output
112#[test]
113fn test_try_parse_utf8_char() {
114 assert_eq!(try_parse_utf8_char(&[0x20]), Some((' ', 1)));
115 assert_eq!(try_parse_utf8_char(&[0x20, 0x20]), Some((' ', 1)));
116 assert_eq!(try_parse_utf8_char(&[0x20, 0xef]), Some((' ', 1)));
118 assert_eq!(try_parse_utf8_char(&[0x00]), Some(('\x00', 1)));
119 assert_eq!(try_parse_utf8_char(&[0x1b]), Some(('\x1b', 1)));
121 assert_eq!(try_parse_utf8_char(&[0xc3, 0xa4]), Some(('ä', 2)));
122 assert_eq!(try_parse_utf8_char(&[0xc3, 0xa4, 0xef]), Some(('ä', 2)));
123 assert_eq!(try_parse_utf8_char(&[0xc3, 0xa4, 0x20]), Some(('ä', 2)));
125 assert_eq!(try_parse_utf8_char(&[0xe2, 0x82, 0xac]), Some(('€', 3)));
126 assert_eq!(
127 try_parse_utf8_char(&[0xe2, 0x82, 0xac, 0xef]),
128 Some(('€', 3))
129 );
130 assert_eq!(
131 try_parse_utf8_char(&[0xe2, 0x82, 0xac, 0x20]),
132 Some(('€', 3))
133 );
135 assert_eq!(try_parse_utf8_char(&[0xe2, 0x88, 0xb0]), Some(('∰', 3)));
137 assert_eq!(
138 try_parse_utf8_char(&[0xf0, 0x9f, 0x8c, 0x82]),
139 Some(('🌂', 4))
140 );
141 assert_eq!(
142 try_parse_utf8_char(&[0xf0, 0x9f, 0x8c, 0x82, 0xef]),
143 Some(('🌂', 4))
144 );
145 assert_eq!(
146 try_parse_utf8_char(&[0xf0, 0x9f, 0x8c, 0x82, 0x20]),
147 Some(('🌂', 4))
148 );
150 assert_eq!(try_parse_utf8_char(&[]), None);
151 assert_eq!(try_parse_utf8_char(&[0xef]), None);
152 assert_eq!(try_parse_utf8_char(&[0xef, 0x20]), None);
153 assert_eq!(try_parse_utf8_char(&[0xf0, 0xf0]), None);