on 7be9485 help sign in
bat/src/vscreen.rs
212 lines 6.2 kB history raw
1use std::fmt::{Display, Formatter};
3// Wrapper to avoid unnecessary branching when input doesn't have ANSI escape sequences.
4pub struct AnsiStyle {
5 attributes: Option<Attributes>,
6}
8impl AnsiStyle {
9 pub fn new() -> Self {
10 AnsiStyle { attributes: None }
11 }
13 pub fn update(&mut self, sequence: &str) -> bool {
14 match &mut self.attributes {
15 Some(a) => a.update(sequence),
16 None => {
17 self.attributes = Some(Attributes::new());
18 self.attributes.as_mut().unwrap().update(sequence)
19 }
20 }
21 }
24impl Display for AnsiStyle {
25 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
26 match self.attributes {
27 Some(ref a) => a.fmt(f),
28 None => Ok(()),
29 }
30 }
33struct Attributes {
34 foreground: String,
35 background: String,
36 underlined: String,
38 /// The character set to use.
39 /// REGEX: `\^[()][AB0-3]`
40 charset: String,
42 /// A buffer for unknown sequences.
43 unknown_buffer: String,
45 /// ON: ^[1m
46 /// OFF: ^[22m
47 bold: String,
49 /// ON: ^[2m
50 /// OFF: ^[22m
51 dim: String,
53 /// ON: ^[4m
54 /// OFF: ^[24m
55 underline: String,
57 /// ON: ^[3m
58 /// OFF: ^[23m
59 italic: String,
61 /// ON: ^[9m
62 /// OFF: ^[29m
63 strike: String,
66impl Attributes {
67 pub fn new() -> Self {
68 Attributes {
69 foreground: "".to_owned(),
70 background: "".to_owned(),
71 underlined: "".to_owned(),
72 charset: "".to_owned(),
73 unknown_buffer: "".to_owned(),
74 bold: "".to_owned(),
75 dim: "".to_owned(),
76 underline: "".to_owned(),
77 italic: "".to_owned(),
78 strike: "".to_owned(),
79 }
80 }
82 /// Update the attributes with an escape sequence.
83 /// Returns `false` if the sequence is unsupported.
84 pub fn update(&mut self, sequence: &str) -> bool {
85 let mut chars = sequence.char_indices().skip(1);
87 if let Some((_, t)) = chars.next() {
88 match t {
89 '(' => self.update_with_charset('(', chars.map(|(_, c)| c)),
90 ')' => self.update_with_charset(')', chars.map(|(_, c)| c)),
91 '[' => {
92 if let Some((i, last)) = chars.last() {
93 // SAFETY: Always starts with ^[ and ends with m.
94 self.update_with_csi(last, &sequence[2..i])
95 } else {
96 false
97 }
98 }
99 _ => self.update_with_unsupported(sequence),
100 }
101 } else {
102 false
103 }
104 }
106 fn sgr_reset(&mut self) {
107 self.foreground.clear();
108 self.background.clear();
109 self.underlined.clear();
110 self.bold.clear();
111 self.dim.clear();
112 self.underline.clear();
113 self.italic.clear();
114 self.strike.clear();
115 }
117 fn update_with_sgr(&mut self, parameters: &str) -> bool {
118 let mut iter = parameters
119 .split(';')
120 .map(|p| if p.is_empty() { "0" } else { p })
121 .map(|p| p.parse::<u16>())
122 .map(|p| p.unwrap_or(0)); // Treat errors as 0.
124 while let Some(p) = iter.next() {
125 match p {
126 0 => self.sgr_reset(),
127 1 => self.bold = format!("\x1B[{}m", parameters),
128 2 => self.dim = format!("\x1B[{}m", parameters),
129 3 => self.italic = format!("\x1B[{}m", parameters),
130 4 => self.underline = format!("\x1B[{}m", parameters),
131 23 => self.italic.clear(),
132 24 => self.underline.clear(),
133 22 => {
134 self.bold.clear();
135 self.dim.clear();
136 }
137 30..=39 => self.foreground = Self::parse_color(p, &mut iter),
138 40..=49 => self.background = Self::parse_color(p, &mut iter),
139 58..=59 => self.underlined = Self::parse_color(p, &mut iter),
140 90..=97 => self.foreground = Self::parse_color(p, &mut iter),
141 100..=107 => self.foreground = Self::parse_color(p, &mut iter),
142 _ => {
143 // Unsupported SGR sequence.
144 // Be compatible and pretend one just wasn't was provided.
145 }
146 }
147 }
149 true
150 }
152 fn update_with_csi(&mut self, finalizer: char, sequence: &str) -> bool {
153 if finalizer == 'm' {
154 self.update_with_sgr(sequence)
155 } else {
156 false
157 }
158 }
160 fn update_with_unsupported(&mut self, sequence: &str) -> bool {
161 self.unknown_buffer.push_str(sequence);
162 false
163 }
165 fn update_with_charset(&mut self, kind: char, set: impl Iterator<Item = char>) -> bool {
166 self.charset = format!("\x1B{}{}", kind, set.take(1).collect::<String>());
167 true
168 }
170 fn parse_color(color: u16, parameters: &mut dyn Iterator<Item = u16>) -> String {
171 match color % 10 {
172 8 => match parameters.next() {
173 Some(5) /* 256-color */ => format!("\x1B[{};5;{}m", color, join(";", 1, parameters)),
174 Some(2) /* 24-bit color */ => format!("\x1B[{};2;{}m", color, join(";", 3, parameters)),
175 Some(c) => format!("\x1B[{};{}m", color, c),
176 _ => "".to_owned(),
177 },
178 9 => "".to_owned(),
179 _ => format!("\x1B[{}m", color),
180 }
181 }
184impl Display for Attributes {
185 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
186 write!(
187 f,
188 "{}{}{}{}{}{}{}{}{}",
189 self.foreground,
190 self.background,
191 self.underlined,
192 self.charset,
193 self.bold,
194 self.dim,
195 self.underline,
196 self.italic,
197 self.strike,
198 )
199 }
202fn join(
203 delimiter: &str,
204 limit: usize,
205 iterator: &mut dyn Iterator<Item = impl ToString>,
206) -> String {
207 iterator
208 .take(limit)
209 .map(|i| i.to_string())
210 .collect::<Vec<String>>()
211 .join(delimiter)