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