Nothing to see here, move along
at main 329 lines 10 kB view raw
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}