Nothing to see here, move along
1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2pub enum Action {
3 Print(u8),
4 Newline,
5 CarriageReturn,
6 Backspace,
7 Tab,
8 SetFg(u32),
9 SetBg(u32),
10 SetBold,
11 SetDim,
12 ResetStyle,
13 DefaultFg,
14 DefaultBg,
15 CursorTo { row: u16, col: u16 },
16 CursorUp(u16),
17 CursorDown(u16),
18 CursorForward(u16),
19 CursorBack(u16),
20 CursorColumn(u16),
21 SaveCursor,
22 RestoreCursor,
23 HideCursor,
24 ShowCursor,
25 ClearToEnd,
26 ClearFromStart,
27 ClearScreen,
28 ClearLineToEnd,
29 ClearLineFromStart,
30 ClearLine,
31 SetScrollRegion { top: u16, bottom: u16 },
32 ResetScrollRegion,
33 ScrollUp(u16),
34 ScrollDown(u16),
35}
36
37#[rustfmt::skip]
38const BASIC_COLORS: [u32; 16] = [
39 0x0000_0000, 0x00AA_0000, 0x0000_AA00, 0x00AA_5500,
40 0x0000_00AA, 0x00AA_00AA, 0x0000_AAAA, 0x00AA_AAAA,
41 0x0055_5555, 0x00FF_5555, 0x0055_FF55, 0x00FF_FF55,
42 0x0055_55FF, 0x00FF_55FF, 0x0055_FFFF, 0x00FF_FFFF,
43];
44
45const MAX_PARAMS: usize = 8;
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48enum State {
49 Ground,
50 Escape,
51 Csi,
52 CsiParam,
53 OscString,
54}
55
56pub struct Parser {
57 state: State,
58 params: [u16; MAX_PARAMS],
59 param_count: usize,
60 current_param: u16,
61 has_digit: bool,
62 private_marker: u8,
63}
64
65impl Parser {
66 pub const fn new() -> Self {
67 Self {
68 state: State::Ground,
69 params: [0; MAX_PARAMS],
70 param_count: 0,
71 current_param: 0,
72 has_digit: false,
73 private_marker: 0,
74 }
75 }
76
77 fn reset_params(&mut self) {
78 self.params = [0; MAX_PARAMS];
79 self.param_count = 0;
80 self.current_param = 0;
81 self.has_digit = false;
82 self.private_marker = 0;
83 }
84
85 fn finish_param(&mut self) {
86 if self.param_count < MAX_PARAMS {
87 self.params[self.param_count] = self.current_param;
88 self.param_count += 1;
89 }
90 self.current_param = 0;
91 self.has_digit = false;
92 }
93
94 fn param(&self, idx: usize, default: u16) -> u16 {
95 match self.params.get(idx) {
96 Some(&v) if v > 0 => v,
97 _ => default,
98 }
99 }
100
101 pub fn feed<F: FnMut(Action)>(&mut self, byte: u8, mut emit: F) {
102 match self.state {
103 State::Ground => self.ground(byte, &mut emit),
104 State::Escape => self.escape(byte, &mut emit),
105 State::Csi => self.csi_entry(byte, &mut emit),
106 State::CsiParam => self.csi_param(byte, &mut emit),
107 State::OscString => self.osc_string(byte),
108 }
109 }
110
111 fn ground<F: FnMut(Action)>(&mut self, byte: u8, emit: &mut F) {
112 match byte {
113 0x1B => self.state = State::Escape,
114 0x0A => emit(Action::Newline),
115 0x0D => emit(Action::CarriageReturn),
116 0x08 => emit(Action::Backspace),
117 0x09 => emit(Action::Tab),
118 0x20..=0x7E => emit(Action::Print(byte)),
119 _ => {}
120 }
121 }
122
123 fn escape<F: FnMut(Action)>(&mut self, byte: u8, emit: &mut F) {
124 match byte {
125 b'[' => {
126 self.reset_params();
127 self.state = State::Csi;
128 }
129 b']' => {
130 self.state = State::OscString;
131 }
132 b'D' => {
133 emit(Action::ScrollUp(1));
134 self.state = State::Ground;
135 }
136 b'M' => {
137 emit(Action::ScrollDown(1));
138 self.state = State::Ground;
139 }
140 _ => {
141 self.state = State::Ground;
142 }
143 }
144 }
145
146 fn csi_entry<F: FnMut(Action)>(&mut self, byte: u8, emit: &mut F) {
147 match byte {
148 b'?' | b'>' | b'!' => {
149 self.private_marker = byte;
150 self.state = State::CsiParam;
151 }
152 b'0'..=b'9' => {
153 self.current_param = (byte - b'0') as u16;
154 self.has_digit = true;
155 self.state = State::CsiParam;
156 }
157 b';' => {
158 self.finish_param();
159 self.state = State::CsiParam;
160 }
161 _ => {
162 self.finish_param();
163 self.dispatch_csi(byte, emit);
164 self.state = State::Ground;
165 }
166 }
167 }
168
169 fn csi_param<F: FnMut(Action)>(&mut self, byte: u8, emit: &mut F) {
170 match byte {
171 b'0'..=b'9' => {
172 self.current_param = self
173 .current_param
174 .saturating_mul(10)
175 .saturating_add((byte - b'0') as u16);
176 self.has_digit = true;
177 }
178 b';' => {
179 self.finish_param();
180 }
181 0x40..=0x7E => {
182 if self.has_digit || self.param_count > 0 {
183 self.finish_param();
184 }
185 self.dispatch_csi(byte, emit);
186 self.state = State::Ground;
187 }
188 _ => {
189 self.state = State::Ground;
190 }
191 }
192 }
193
194 fn osc_string(&mut self, byte: u8) {
195 match byte {
196 0x07 | 0x1B => {
197 self.state = State::Ground;
198 }
199 _ => {}
200 }
201 }
202
203 fn dispatch_csi<F: FnMut(Action)>(&mut self, final_byte: u8, emit: &mut F) {
204 if self.private_marker == b'?' {
205 match final_byte {
206 b'l' if self.param(0, 0) == 25 => emit(Action::HideCursor),
207 b'h' if self.param(0, 0) == 25 => emit(Action::ShowCursor),
208 _ => {}
209 }
210 return;
211 }
212
213 match final_byte {
214 b'H' | b'f' => {
215 let row = self.param(0, 1);
216 let col = self.param(1, 1);
217 emit(Action::CursorTo { row, col });
218 }
219 b'A' => emit(Action::CursorUp(self.param(0, 1))),
220 b'B' => emit(Action::CursorDown(self.param(0, 1))),
221 b'C' => emit(Action::CursorForward(self.param(0, 1))),
222 b'D' => emit(Action::CursorBack(self.param(0, 1))),
223 b'G' => emit(Action::CursorColumn(self.param(0, 1))),
224 b's' => emit(Action::SaveCursor),
225 b'u' => emit(Action::RestoreCursor),
226 b'J' => match self.param(0, 0) {
227 0 => emit(Action::ClearToEnd),
228 1 => emit(Action::ClearFromStart),
229 2 | 3 => emit(Action::ClearScreen),
230 _ => {}
231 },
232 b'K' => match self.param(0, 0) {
233 0 => emit(Action::ClearLineToEnd),
234 1 => emit(Action::ClearLineFromStart),
235 2 => emit(Action::ClearLine),
236 _ => {}
237 },
238 b'r' => match self.param_count {
239 0 => emit(Action::ResetScrollRegion),
240 _ => {
241 let top = self.param(0, 1);
242 let bottom = self.param(1, 0);
243 match bottom {
244 0 => emit(Action::ResetScrollRegion),
245 _ => emit(Action::SetScrollRegion { top, bottom }),
246 }
247 }
248 },
249 b'S' => emit(Action::ScrollUp(self.param(0, 1))),
250 b'T' => emit(Action::ScrollDown(self.param(0, 1))),
251 b'm' => self.dispatch_sgr(emit),
252 _ => {}
253 }
254 }
255
256 fn dispatch_sgr<F: FnMut(Action)>(&mut self, emit: &mut F) {
257 if self.param_count == 0 {
258 emit(Action::ResetStyle);
259 return;
260 }
261
262 let mut i = 0;
263 let count = self.param_count;
264 let params = self.params;
265
266 while i < count {
267 match params[i] {
268 0 => emit(Action::ResetStyle),
269 1 => emit(Action::SetBold),
270 2 => emit(Action::SetDim),
271 22 => emit(Action::ResetStyle),
272 30..=37 => {
273 let idx = (params[i] - 30) as usize;
274 emit(Action::SetFg(BASIC_COLORS[idx]));
275 }
276 38 => {
277 if i + 1 < count && params[i + 1] == 2 && i + 4 < count {
278 let r = params[i + 2] as u8;
279 let g = params[i + 3] as u8;
280 let b = params[i + 4] as u8;
281 emit(Action::SetFg(
282 ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
283 ));
284 i += 4;
285 } else if i + 1 < count && params[i + 1] == 5 && i + 2 < count {
286 let idx = params[i + 2] as usize;
287 if idx < 16 {
288 emit(Action::SetFg(BASIC_COLORS[idx]));
289 }
290 i += 2;
291 }
292 }
293 39 => emit(Action::DefaultFg),
294 40..=47 => {
295 let idx = (params[i] - 40) as usize;
296 emit(Action::SetBg(BASIC_COLORS[idx]));
297 }
298 48 => {
299 if i + 1 < count && params[i + 1] == 2 && i + 4 < count {
300 let r = params[i + 2] as u8;
301 let g = params[i + 3] as u8;
302 let b = params[i + 4] as u8;
303 emit(Action::SetBg(
304 ((r as u32) << 16) | ((g as u32) << 8) | (b as u32),
305 ));
306 i += 4;
307 } else if i + 1 < count && params[i + 1] == 5 && i + 2 < count {
308 let idx = params[i + 2] as usize;
309 if idx < 16 {
310 emit(Action::SetBg(BASIC_COLORS[idx]));
311 }
312 i += 2;
313 }
314 }
315 49 => emit(Action::DefaultBg),
316 90..=97 => {
317 let idx = (params[i] - 90 + 8) as usize;
318 emit(Action::SetFg(BASIC_COLORS[idx]));
319 }
320 100..=107 => {
321 let idx = (params[i] - 100 + 8) as usize;
322 emit(Action::SetBg(BASIC_COLORS[idx]));
323 }
324 _ => {}
325 }
326 i += 1;
327 }
328 }
329}