1mod cell;
9mod colour;
10mod commands;
11mod csi_helpers;
12mod dispatch;
13pub mod mode;
14mod params;
15mod passthrough;
16mod recovery;
17mod sgr;
18mod states;
19mod tables;
20#[cfg(test)]
21mod tests;
22mod writer;
23
24pub use cell::{CellState, GridAttr, SavedState};
25pub use colour::{
26 colour_join_rgb, Colour, COLOUR_DEFAULT, COLOUR_FLAG_256, COLOUR_FLAG_RGB, COLOUR_NONE,
27 COLOUR_TERMINAL,
28};
29pub use dispatch::{CsiCommand, DcsPayload, EscCommand, InputAction, OscCommand, ScreenWriter};
30pub use params::{InputParam, ParamType};
31pub use states::InputState;
32
33use params::ParamList;
34use states::Transition;
35
36use crate::terminal_passthrough::MAX_TERMINAL_PASSTHROUGH_PAYLOAD_BYTES;
37
38const PARAM_LIST_MAX: usize = 24;
40
41const INTERM_BUF_MAX: usize = 4;
43
44const INPUT_BUF_START: usize = 32;
46
47const INPUT_BUF_MAX: usize = 1_048_576;
49
50const PARAM_BUF_MAX: usize = 64;
52
53const INPUT_DISCARD: u32 = 0x1;
55const INPUT_LAST: u32 = 0x2;
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum InputEndType {
61 St,
63 Bel,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum OscColourSlot {
70 Foreground,
72 Background,
74 Cursor,
76}
77
78impl OscColourSlot {
79 pub(crate) const fn osc_number(self) -> u32 {
80 match self {
81 Self::Foreground => 10,
82 Self::Background => 11,
83 Self::Cursor => 12,
84 }
85 }
86
87 const fn index(self) -> usize {
88 match self {
89 Self::Foreground => 0,
90 Self::Background => 1,
91 Self::Cursor => 2,
92 }
93 }
94}
95
96pub struct InputParser {
98 state: InputState,
100 flags: u32,
102
103 ch: u8,
105
106 interm_buf: [u8; INTERM_BUF_MAX],
108 interm_len: usize,
109
110 param_buf: [u8; PARAM_BUF_MAX],
112 param_len: usize,
113
114 input_buf: Vec<u8>,
116 input_buf_max: usize,
117 input_end: InputEndType,
119
120 param_list: ParamList,
122
123 cell: CellState,
125 saved: SavedState,
127
128 utf8_buf: [u8; 4],
130 utf8_len: u8,
131 utf8_expected: u8,
132 utf8_started: bool,
133
134 last_char: Option<char>,
136 printed_in_current_parse: bool,
138 recovery_rebase_required: bool,
140
141 since_ground: Vec<u8>,
143
144 ground_timer_active: bool,
147
148 reply_buf: Vec<u8>,
150 terminal_passthrough_dropped_count: u64,
152
153 osc_colours: [Option<String>; 3],
156}
157
158impl InputParser {
159 #[must_use]
161 pub fn new() -> Self {
162 Self {
163 state: InputState::Ground,
164 flags: 0,
165 ch: 0,
166 interm_buf: [0; INTERM_BUF_MAX],
167 interm_len: 0,
168 param_buf: [0; PARAM_BUF_MAX],
169 param_len: 0,
170 input_buf: Vec::with_capacity(INPUT_BUF_START),
171 input_buf_max: INPUT_BUF_MAX,
172 input_end: InputEndType::St,
173 param_list: ParamList::new(),
174 cell: CellState::default(),
175 saved: SavedState::default(),
176 utf8_buf: [0; 4],
177 utf8_len: 0,
178 utf8_expected: 0,
179 utf8_started: false,
180 last_char: None,
181 printed_in_current_parse: false,
182 recovery_rebase_required: false,
183 since_ground: Vec::new(),
184 ground_timer_active: false,
185 reply_buf: Vec::new(),
186 terminal_passthrough_dropped_count: 0,
187 osc_colours: [None, None, None],
188 }
189 }
190
191 pub(crate) fn osc_colour(&self, slot: OscColourSlot) -> Option<&str> {
193 self.osc_colours[slot.index()].as_deref()
194 }
195
196 pub(crate) fn set_osc_colour(&mut self, slot: OscColourSlot, value: &str) {
199 const OSC_COLOUR_MAX_LEN: usize = 64;
208 if value.len() > OSC_COLOUR_MAX_LEN {
209 return;
210 }
211 self.osc_colours[slot.index()] = Some(value.to_owned());
212 }
213
214 pub(crate) fn reset_osc_colour(&mut self, slot: OscColourSlot) {
216 self.osc_colours[slot.index()] = None;
217 }
218
219 pub fn set_input_buffer_limit(&mut self, limit: usize) {
221 self.input_buf_max = limit.max(INPUT_BUF_START);
222 }
223
224 pub(crate) const fn configured_input_buffer_limit(&self) -> usize {
225 self.input_buf_max
226 }
227
228 #[must_use]
230 pub fn state(&self) -> InputState {
231 self.state
232 }
233
234 pub fn take_replies(&mut self) -> Vec<u8> {
236 std::mem::take(&mut self.reply_buf)
237 }
238
239 pub(crate) fn take_terminal_passthrough_dropped_count(&mut self) -> u64 {
241 let dropped = self.terminal_passthrough_dropped_count;
242 self.terminal_passthrough_dropped_count = 0;
243 dropped
244 }
245
246 pub fn take_since_ground(&mut self) -> Vec<u8> {
248 std::mem::take(&mut self.since_ground)
249 }
250
251 #[must_use]
253 pub fn pending_bytes(&self) -> Vec<u8> {
254 self.pending_bytes_ref().to_vec()
255 }
256
257 #[must_use]
259 pub(crate) fn pending_bytes_ref(&self) -> &[u8] {
260 if self.state != InputState::Ground {
261 return &self.since_ground;
262 }
263 if self.utf8_started {
264 return &self.utf8_buf[..usize::from(self.utf8_len)];
265 }
266 &[]
267 }
268
269 #[must_use]
271 pub fn ground_timer_active(&self) -> bool {
272 self.ground_timer_active
273 }
274
275 pub fn ground_timer_expired(&mut self) {
277 self.reset_to_ground();
278 }
279
280 pub fn reset_to_ground(&mut self) {
282 self.clear();
283 self.state = InputState::Ground;
284 self.flags = 0;
285 self.enter_ground();
286 }
287
288 #[must_use]
290 pub fn cell_state(&self) -> &CellState {
291 &self.cell
292 }
293
294 #[must_use]
296 pub const fn saved_state(&self) -> &SavedState {
297 &self.saved
298 }
299
300 pub(crate) fn plain_output_forwarding_safe(&self) -> bool {
301 self.state == InputState::Ground && !self.utf8_started && self.cell == CellState::default()
302 }
303
304 pub fn parse<W: ScreenWriter + ?Sized>(&mut self, buf: &[u8], writer: &mut W) {
306 self.printed_in_current_parse = false;
307 let mut index = 0;
308 while index < buf.len() {
309 if self.state == InputState::Ground && !self.utf8_started {
310 let printable_end = buf[index..]
311 .iter()
312 .position(|byte| !byte.is_ascii_graphic() && *byte != b' ')
313 .map_or(buf.len(), |offset| index + offset);
314 if printable_end > index {
315 self.handle_printable_ascii_run(&buf[index..printable_end], writer);
316 index = printable_end;
317 continue;
318 }
319 if self.handle_ground_c0_fast_path(buf[index], writer) {
320 index += 1;
321 continue;
322 }
323 }
324
325 self.ch = buf[index];
326 let transition = self.find_transition();
327 self.execute_transition(transition, writer);
328 index += 1;
329 }
330 }
331
332 fn handle_printable_ascii_run<W: ScreenWriter + ?Sized>(
333 &mut self,
334 bytes: &[u8],
335 writer: &mut W,
336 ) {
337 debug_assert_eq!(self.state, InputState::Ground);
338 let set = if self.cell.set == 0 {
339 self.cell.g0set
340 } else {
341 self.cell.g1set
342 };
343 let acs = set != 0;
344 writer.collect_add_ascii_run(bytes, &self.cell, acs);
345 if let Some(&last) = bytes.last() {
346 self.last_char = Some(char::from(last));
347 self.printed_in_current_parse = true;
348 }
349 self.flags |= INPUT_LAST;
350 }
351
352 fn handle_ground_c0_fast_path<W: ScreenWriter + ?Sized>(
353 &mut self,
354 byte: u8,
355 writer: &mut W,
356 ) -> bool {
357 match byte {
358 0x0a..=0x0c => {
359 writer.collect_end();
360 writer.linefeed(false, self.cell.bg());
361 if writer.current_mode() & mode::MODE_CRLF != 0 {
362 writer.carriage_return();
363 }
364 }
365 0x0d => {
366 writer.collect_end();
367 writer.carriage_return();
368 }
369 _ => return false,
370 }
371 self.flags &= !INPUT_LAST;
372 true
373 }
374
375 fn find_transition(&self) -> Transition {
376 self.state.transition_for_byte(self.ch)
377 }
378
379 fn execute_transition<W: ScreenWriter + ?Sized>(&mut self, trans: Transition, writer: &mut W) {
380 if !matches!(
382 trans.handler,
383 states::Handler::Print | states::Handler::TopBitSet
384 ) {
385 writer.collect_end();
386 }
387
388 let skip_state = match trans.handler {
390 states::Handler::None => false,
391 states::Handler::Print => self.handle_print(writer),
392 states::Handler::C0Dispatch => self.handle_c0_dispatch(writer),
393 states::Handler::EscDispatch => self.handle_esc_dispatch(writer),
394 states::Handler::CsiDispatch => self.handle_csi_dispatch(writer),
395 states::Handler::DcsDispatch => self.handle_dcs_dispatch(writer),
396 states::Handler::Intermediate => self.handle_intermediate(),
397 states::Handler::Parameter => self.handle_parameter(),
398 states::Handler::Input => self.handle_input(),
399 states::Handler::TopBitSet => self.handle_top_bit_set(writer),
400 states::Handler::EndBel => self.handle_end_bel(),
401 };
402
403 if skip_state {
404 return;
405 }
406
407 if self.should_recover_tmux_passthrough_osc_bel() {
408 self.input_end = InputEndType::Bel;
409 self.handle_dcs_dispatch(writer);
410 self.set_state(InputState::Ground, writer);
411 return;
412 }
413
414 if let Some(next) = trans.next_state {
415 self.set_state(next, writer);
416 }
417
418 if self.state != InputState::Ground && self.since_ground.len() < self.input_buf_max {
420 self.since_ground.push(self.ch);
421 }
422 }
423
424 fn set_state<W: ScreenWriter + ?Sized>(&mut self, next: InputState, writer: &mut W) {
425 self.exit_state(writer);
427 self.state = next;
428 self.enter_state(writer);
430 }
431
432 fn enter_state<W: ScreenWriter + ?Sized>(&mut self, writer: &mut W) {
433 match self.state {
434 InputState::Ground => self.enter_ground(),
435 InputState::EscEnter => self.clear(),
436 InputState::CsiEnter => self.clear(),
437 InputState::DcsEnter => self.enter_dcs(),
438 InputState::OscString => self.enter_osc(),
439 InputState::ApcString => self.enter_apc(),
440 InputState::RenameString => self.enter_rename(),
441 InputState::ConsumeSt => self.enter_rename(), _ => {}
443 }
444 let _ = writer; }
446
447 fn exit_state<W: ScreenWriter + ?Sized>(&mut self, writer: &mut W) {
448 match self.state {
449 InputState::OscString => self.exit_osc(writer),
450 InputState::ApcString => self.exit_apc(writer),
451 InputState::RenameString => self.exit_rename(writer),
452 _ => {}
453 }
454 }
455
456 fn clear(&mut self) {
457 self.ground_timer_active = false;
458 self.interm_buf = [0; INTERM_BUF_MAX];
459 self.interm_len = 0;
460 self.param_buf = [0; PARAM_BUF_MAX];
461 self.param_len = 0;
462 self.input_buf.clear();
463 self.input_end = InputEndType::St;
464 self.flags &= !INPUT_DISCARD;
465 }
466
467 fn enter_ground(&mut self) {
468 self.ground_timer_active = false;
469 self.since_ground.clear();
470 if self.input_buf.capacity() > INPUT_BUF_START {
472 self.input_buf = Vec::with_capacity(INPUT_BUF_START);
473 }
474 }
475
476 fn enter_dcs(&mut self) {
477 self.clear();
478 self.ground_timer_active = true;
479 self.flags &= !INPUT_LAST;
480 }
481
482 fn enter_osc(&mut self) {
483 self.clear();
484 self.ground_timer_active = true;
485 self.flags &= !INPUT_LAST;
486 }
487
488 fn enter_apc(&mut self) {
489 self.clear();
490 self.ground_timer_active = true;
491 self.flags &= !INPUT_LAST;
492 }
493
494 fn enter_rename(&mut self) {
495 self.clear();
496 self.ground_timer_active = true;
497 self.flags &= !INPUT_LAST;
498 }
499
500 fn exit_osc<W: ScreenWriter + ?Sized>(&mut self, writer: &mut W) {
501 if self.flags & INPUT_DISCARD != 0 {
502 return;
503 }
504 dispatch::dispatch_osc(self, writer);
505 }
506
507 fn exit_apc<W: ScreenWriter + ?Sized>(&mut self, writer: &mut W) {
508 if self.flags & INPUT_DISCARD != 0 {
509 return;
510 }
511 if passthrough::is_kitty_graphics_apc(&self.input_buf) {
512 writer.apc_passthrough(&self.input_buf);
513 return;
514 }
515 let buf = String::from_utf8_lossy(&self.input_buf).into_owned();
516 writer.set_title(&buf);
517 }
518
519 fn exit_rename<W: ScreenWriter + ?Sized>(&mut self, writer: &mut W) {
520 if self.flags & INPUT_DISCARD != 0 {
521 return;
522 }
523 let buf = String::from_utf8_lossy(&self.input_buf).into_owned();
524 writer.set_window_name(&buf);
525 }
526
527 fn stop_utf8<W: ScreenWriter + ?Sized>(&mut self, writer: &mut W) {
529 if self.utf8_started {
530 writer.collect_add('\u{FFFD}', &self.cell);
531 self.utf8_started = false;
532 self.utf8_len = 0;
533 self.utf8_expected = 0;
534 }
535 }
536
537 fn handle_print<W: ScreenWriter + ?Sized>(&mut self, writer: &mut W) -> bool {
538 self.stop_utf8(writer);
539
540 let ch = self.ch as char;
541 let set = if self.cell.set == 0 {
542 self.cell.g0set
543 } else {
544 self.cell.g1set
545 };
546
547 writer.collect_add_with_charset(ch, &self.cell, set != 0);
548
549 self.last_char = Some(ch);
550 self.printed_in_current_parse = true;
551 self.flags |= INPUT_LAST;
552
553 false
554 }
555
556 fn handle_intermediate(&mut self) -> bool {
557 if self.interm_len >= INTERM_BUF_MAX - 1 {
558 self.flags |= INPUT_DISCARD;
559 } else {
560 self.interm_buf[self.interm_len] = self.ch;
561 self.interm_len += 1;
562 }
563 false
564 }
565
566 fn handle_parameter(&mut self) -> bool {
567 if self.param_len >= PARAM_BUF_MAX - 1 {
568 self.flags |= INPUT_DISCARD;
569 } else {
570 self.param_buf[self.param_len] = self.ch;
571 self.param_len += 1;
572 }
573 false
574 }
575
576 fn handle_input(&mut self) -> bool {
577 let escaped_dcs_byte = self.state == InputState::DcsEscape;
578 let bytes_to_push = if escaped_dcs_byte && self.ch != 0x1b {
579 2
580 } else {
581 1
582 };
583 let input_limit = self.input_buffer_limit();
584 if self.input_buf.len() + bytes_to_push >= input_limit {
585 if self.flags & INPUT_DISCARD == 0 && self.is_terminal_passthrough_string() {
586 self.terminal_passthrough_dropped_count =
587 self.terminal_passthrough_dropped_count.saturating_add(1);
588 }
589 self.flags |= INPUT_DISCARD;
590 } else if escaped_dcs_byte && self.ch == 0x1b {
591 self.input_buf.push(0x1b);
592 } else if escaped_dcs_byte {
593 self.input_buf.push(0x1b);
594 self.input_buf.push(self.ch);
595 } else {
596 self.input_buf.push(self.ch);
597 }
598 false
599 }
600
601 fn input_buffer_limit(&self) -> usize {
602 if self.is_terminal_passthrough_string() {
603 return MAX_TERMINAL_PASSTHROUGH_PAYLOAD_BYTES;
604 }
605 self.input_buf_max
606 }
607
608 fn is_terminal_passthrough_string(&self) -> bool {
609 (self.state == InputState::ApcString && passthrough::is_kitty_graphics_apc(&self.input_buf))
610 || (matches!(self.state, InputState::DcsHandler | InputState::DcsEscape)
611 && self.interm_len == 0
612 && (self.input_buf.first() == Some(&b'q') || self.input_buf.starts_with(b"tmux;")))
613 }
614
615 fn should_recover_tmux_passthrough_osc_bel(&self) -> bool {
616 if self.state != InputState::DcsHandler || self.ch != 0x07 || self.interm_len != 0 {
617 return false;
618 }
619
620 let Some(payload) = self.input_buf.strip_prefix(b"tmux;") else {
621 return false;
622 };
623
624 payload.starts_with(b"\x1b]") || payload.first() == Some(&0x9d)
625 }
626
627 fn handle_end_bel(&mut self) -> bool {
628 self.input_end = InputEndType::Bel;
629 false
630 }
631
632 fn handle_c0_dispatch<W: ScreenWriter + ?Sized>(&mut self, writer: &mut W) -> bool {
633 self.stop_utf8(writer);
634 dispatch::dispatch_c0(self, writer);
635 self.flags &= !INPUT_LAST;
636 false
637 }
638
639 fn handle_esc_dispatch<W: ScreenWriter + ?Sized>(&mut self, writer: &mut W) -> bool {
640 if self.flags & INPUT_DISCARD != 0 {
641 return false;
642 }
643 dispatch::dispatch_esc(self, writer);
644 self.flags &= !INPUT_LAST;
645 false
646 }
647
648 fn handle_csi_dispatch<W: ScreenWriter + ?Sized>(&mut self, writer: &mut W) -> bool {
649 if self.flags & INPUT_DISCARD != 0 {
650 return false;
651 }
652 dispatch::dispatch_csi(self, writer);
653 self.flags &= !INPUT_LAST;
654 false
655 }
656
657 fn handle_dcs_dispatch<W: ScreenWriter + ?Sized>(&mut self, writer: &mut W) -> bool {
658 if self.flags & INPUT_DISCARD != 0 {
659 return false;
660 }
661 dispatch::dispatch_dcs(self, writer);
662 false
663 }
664
665 fn handle_top_bit_set<W: ScreenWriter + ?Sized>(&mut self, writer: &mut W) -> bool {
666 self.flags &= !INPUT_LAST;
667
668 if !self.utf8_started {
669 self.utf8_started = true;
670 self.utf8_len = 0;
671 let expected = if self.ch & 0xE0 == 0xC0 {
673 2
674 } else if self.ch & 0xF0 == 0xE0 {
675 3
676 } else if self.ch & 0xF8 == 0xF0 {
677 4
678 } else {
679 self.stop_utf8(writer);
681 return false;
682 };
683 self.utf8_expected = expected;
684 self.utf8_buf[0] = self.ch;
685 self.utf8_len = 1;
686 return false;
687 }
688
689 if self.ch & 0xC0 != 0x80 {
691 self.stop_utf8(writer);
693 if self.ch >= 0x80 {
695 return self.handle_top_bit_set(writer);
696 }
697 return false;
698 }
699
700 self.utf8_buf[self.utf8_len as usize] = self.ch;
701 self.utf8_len += 1;
702
703 if self.utf8_len < self.utf8_expected {
704 return false; }
706
707 self.utf8_started = false;
709 let bytes = &self.utf8_buf[..self.utf8_len as usize];
710 let s = match std::str::from_utf8(bytes) {
711 Ok(s) => s,
712 Err(_) => {
713 writer.collect_add('\u{FFFD}', &self.cell);
714 return false;
715 }
716 };
717 let c = match s.chars().next() {
718 Some(c) => c,
719 None => {
720 writer.collect_add('\u{FFFD}', &self.cell);
721 return false;
722 }
723 };
724
725 writer.collect_add(c, &self.cell);
726
727 self.last_char = Some(c);
728 self.printed_in_current_parse = true;
729 self.flags |= INPUT_LAST;
730
731 false
732 }
733
734 fn reply(&mut self, s: &str) {
736 self.reply_buf.extend_from_slice(s.as_bytes());
737 }
738
739 fn interm_str(&self) -> &[u8] {
741 &self.interm_buf[..self.interm_len]
742 }
743
744 fn note_effective_rep(&mut self) {
745 if !self.printed_in_current_parse {
746 self.recovery_rebase_required = true;
747 }
748 }
749
750 #[must_use]
753 pub(crate) fn take_recovery_rebase_required(&mut self) -> bool {
754 std::mem::take(&mut self.recovery_rebase_required)
755 }
756}
757
758impl Default for InputParser {
759 fn default() -> Self {
760 Self::new()
761 }
762}