1use std::collections::BTreeMap;
16
17use crate::{
18 cell::Cell,
19 screen::{SavedCursor, Screen},
20 term::{
21 AsTermInput, BlinkStyle, ControlCodes, FontWeight, FrameStyle, LinkTarget, OriginMode,
22 Region, UnderlineStyle,
23 },
24};
25
26use bitvec::{bitvec, vec::BitVec};
27use smallvec::SmallVec;
28use unicode_width::UnicodeWidthChar;
29
30#[macro_use]
31mod visibility;
32
33#[macro_use]
34mod log;
35
36mod altscreen;
37mod cell;
38mod line;
39mod screen;
40mod scrollback;
41
42#[cfg(not(feature = "unstable-internal-test"))]
43mod term;
44
45#[cfg(feature = "unstable-internal-test")]
46pub mod term;
47
48const MAX_TITLE_STACK_DEPTH: usize = 64;
49
50pub struct Term {
52 parser: vte::Parser,
53 state: State,
54 logger: log::Context,
55}
56
57impl Term {
58 pub fn new(scrollback_lines: usize, size: Size) -> Self {
67 Term {
68 parser: vte::Parser::new(),
69 state: State::new(scrollback_lines, size),
70 logger: log::Context::None,
71 }
72 }
73
74 pub fn tag(&mut self, tag: String) {
78 let logger = log::Context::Tag(tag);
79 self.logger = logger.clone();
80 self.state.set_logger(logger);
81 }
82
83 pub fn size(&self) -> Size {
85 self.state.screen().size
86 }
87
88 pub fn resize(&mut self, size: Size) {
93 if size.height > self.scrollback_lines() {
94 self.set_scrollback_lines(size.height);
95 }
96
97 self.state.resize(size);
98 }
99
100 pub fn scrollback_lines(&self) -> usize {
102 self.state.scrollback.scrollback_lines().expect("scrollback screen to have lines")
103 }
104
105 pub fn set_scrollback_lines(&mut self, scrollback_lines: usize) {
113 self.state.scrollback.set_scrollback_lines(scrollback_lines);
114 }
115
116 pub fn process(&mut self, buf: &[u8]) {
119 self.parser.advance(&mut self.state, buf);
120 }
121
122 pub fn contents(&self, dump_region: ContentRegion) -> Vec<u8> {
127 let mut buf = vec![];
128
129 term::control_codes().end_link.term_input_into(&mut buf);
133
134 term::control_codes().clear_attrs.term_input_into(&mut buf);
135
136 term::control_codes().unset_scroll_region.term_input_into(&mut buf);
141 term::control_codes().disable_scroll_region_origin_mode.term_input_into(&mut buf);
142
143 term::ControlCodes::cursor_position(1, 1).term_input_into(&mut buf);
144 term::control_codes().clear_screen.term_input_into(&mut buf);
145 self.state.dump_contents_into(&mut buf, dump_region);
146
147 buf
148 }
149}
150
151#[derive(Debug, Eq, PartialEq, Clone)]
153pub enum ContentRegion {
154 All,
156 Screen,
158 BottomLines(usize),
160}
161
162impl std::fmt::Display for Term {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 self.state.fmt(f)
165 }
166}
167
168const MOUSE_MODES: [u16; 7] = [1000, 1002, 1003, 1005, 1006, 1015, 1016];
177
178fn mouse_mode_idx(param: &[u16]) -> Option<usize> {
180 match param {
181 [mode] => MOUSE_MODES.iter().position(|m| m == mode),
182 _ => None,
183 }
184}
185
186#[derive(Debug, Clone, Copy, Eq, PartialEq)]
188pub struct Size {
189 pub width: usize,
190 pub height: usize,
191}
192
193struct State {
195 scrollback: Screen,
197 altscreen: Screen,
199 screen_mode: ScreenMode,
201 last_print_char: Option<char>,
204 cursor_attrs: term::Attrs,
209 cursor_style: term::CursorStyle,
212 title_stack: Vec<SmallVec<[u8; 8]>>,
214 icon_name_stack: Vec<SmallVec<[u8; 8]>>,
216 working_dir: Option<WorkingDir>,
219 palette_overrides: BTreeMap<usize, Vec<u8>>,
223 functional_colors: [Option<Vec<u8>>; 10],
226 cursor_hidden: bool,
229 cursor_blinking: Option<bool>,
231 application_cursor_keys_enabled: bool,
234 application_keypad_mode_enabled: bool,
240 mouse_modes: [bool; MOUSE_MODES.len()],
248 report_focus: bool,
255 in_paste_mode: bool,
257 insert_mode: bool,
259 tabstops: BitVec,
263 logger: log::Context,
264}
265
266struct WorkingDir {
267 host: SmallVec<[u8; 8]>,
268 dir: SmallVec<[u8; 8]>,
269}
270
271impl std::fmt::Display for State {
272 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273 match self.screen_mode {
274 ScreenMode::Scrollback => {
275 writeln!(f, "Screen Mode: Scrollback")?;
276 write!(f, "{}", self.scrollback)?;
277 }
278 ScreenMode::Alt => {
279 writeln!(f, "Screen Mode: AltScreen")?;
280 write!(f, "{}", self.altscreen)?;
281 }
282 }
283
284 Ok(())
285 }
286}
287
288impl State {
289 fn new(scrollback_lines: usize, size: Size) -> Self {
290 let mut st = State {
291 scrollback: Screen::scrollback(scrollback_lines, size),
292 altscreen: Screen::alt(size),
293 screen_mode: ScreenMode::Scrollback,
294 cursor_attrs: term::Attrs::default(),
295 cursor_style: term::CursorStyle::Default,
296 title_stack: vec![],
297 icon_name_stack: vec![],
298 working_dir: None,
299 palette_overrides: BTreeMap::new(),
300 functional_colors: [NONE_VEC; 10],
301 cursor_hidden: false,
302 cursor_blinking: None,
303 application_cursor_keys_enabled: false,
304 application_keypad_mode_enabled: false,
305 mouse_modes: [false; MOUSE_MODES.len()],
306 report_focus: false,
307 in_paste_mode: false,
308 insert_mode: false,
309 tabstops: bitvec![0; size.width],
310 last_print_char: None,
311 logger: log::Context::None,
312 };
313 st.fill_tabstops(0, size.width);
314 st
315 }
316
317 fn set_logger(&mut self, logger: log::Context) {
318 self.scrollback.set_logger(logger.clone());
319 self.altscreen.set_logger(logger.clone());
320 self.logger = logger;
321 }
322
323 fn screen_mut(&mut self) -> &mut Screen {
324 match self.screen_mode {
325 ScreenMode::Scrollback => &mut self.scrollback,
326 ScreenMode::Alt => &mut self.altscreen,
327 }
328 }
329
330 fn screen(&self) -> &Screen {
331 match self.screen_mode {
332 ScreenMode::Scrollback => &self.scrollback,
333 ScreenMode::Alt => &self.altscreen,
334 }
335 }
336
337 fn resize(&mut self, size: Size) {
338 let orig_len = self.tabstops.len();
339 self.tabstops.resize(size.width, false);
340 if size.width > orig_len {
341 self.fill_tabstops(orig_len, size.width);
342 }
343
344 self.scrollback.resize(size);
345 self.altscreen.resize(size);
346 }
347
348 fn fill_tabstops(&mut self, start: usize, end: usize) {
350 assert!(end <= self.tabstops.len());
351
352 for i in start..end {
353 if i > 0 && i % 8 == 0 {
354 self.tabstops.set(i, true);
355 }
356 }
357 }
358
359 fn dump_tabstops(&self, buf: &mut Vec<u8>) {
364 let controls = term::control_codes();
365 if self.tabstops.len() > 8 && self.tabstops.not_any() {
366 ControlCodes::tab_clear(Some(3)).term_input_into(buf);
369 return;
370 }
371
372 let mut codes = vec![];
373 for i in 0..self.tabstops.len() {
374 let bit = self.tabstops.get(i).is_some_and(|b| *b);
375 let i: u16 = match i.try_into() {
376 Ok(i) => i,
377 Err(e) => {
378 warn!(self.logger, "generating tabstop codes: index out of bounds: {:?}", e);
379 return;
380 }
381 };
382 if i > 0 && i % 8 == 0 {
383 if !bit {
385 codes.push(ControlCodes::cursor_position(1, i + 1));
386 codes.push(ControlCodes::tab_clear(None));
387 }
388 } else {
389 if bit {
391 codes.push(ControlCodes::cursor_position(1, i + 1));
392 codes.push(controls.horizontal_tab_set.clone());
393 }
394 }
395 }
396
397 if !codes.is_empty() {
398 for code in codes.into_iter() {
399 code.term_input_into(buf);
400 }
401 ControlCodes::cursor_position(1, 1).term_input_into(buf);
402 }
403 }
404
405 fn dump_contents_into(&self, buf: &mut Vec<u8>, dump_region: ContentRegion) {
406 self.dump_tabstops(buf);
407
408 match self.screen_mode {
409 ScreenMode::Scrollback => self.scrollback.dump_contents_into(buf, dump_region),
410 ScreenMode::Alt => {
411 self.scrollback.dump_contents_into(buf, dump_region.clone());
414
415 term::control_codes().enable_alt_screen.term_input_into(buf);
422
423 self.scrollback.dump_global_state_reset_into(buf);
430
431 self.altscreen.dump_contents_into(buf, dump_region)
432 }
433 }
434
435 let controls = term::control_codes();
436
437 controls.clear_attrs.term_input_into(buf);
440 let mut cursor_attrs = self.cursor_attrs.clone();
441 cursor_attrs.link_target = None;
447 let codes = term::Attrs::default().transition_to(&cursor_attrs);
448 for c in codes.into_iter() {
449 c.term_input_into(buf);
450 }
451 if self.cursor_style != term::CursorStyle::Default {
452 self.cursor_style.term_input_into(buf);
453 }
454
455 match (self.title_stack.last(), self.icon_name_stack.last()) {
460 (Some(title), Some(icon_name)) if !title.is_empty() && title == icon_name => {
461 ControlCodes::set_title_and_icon_name(title.clone()).term_input_into(buf)
462 }
463 (Some(title), Some(icon_name)) => {
464 if !title.is_empty() {
465 ControlCodes::set_title(title.clone()).term_input_into(buf);
466 }
467 if !icon_name.is_empty() {
468 ControlCodes::set_icon_name(icon_name.clone()).term_input_into(buf);
469 }
470 }
471 (Some(title), None) => {
472 if !title.is_empty() {
473 ControlCodes::set_title(title.clone()).term_input_into(buf);
474 }
475 }
476 (None, Some(icon_name)) => {
477 if !icon_name.is_empty() {
478 ControlCodes::set_icon_name(icon_name.clone()).term_input_into(buf);
479 }
480 }
481 (None, None) => {}
482 }
483
484 if let Some(working_dir) = &self.working_dir {
485 ControlCodes::set_working_dir(working_dir.host.clone(), working_dir.dir.clone())
486 .term_input_into(buf);
487 }
488
489 if !self.palette_overrides.is_empty() {
490 ControlCodes::set_color_indices(
491 self.palette_overrides
492 .iter()
493 .map(|(idx, color_spec)| (*idx, SmallVec::from(color_spec.as_slice()))),
494 )
495 .term_input_into(buf);
496 }
497
498 if self.cursor_hidden {
499 controls.hide_cursor.term_input_into(buf);
500 }
501 if let Some(blinking) = self.cursor_blinking {
502 if blinking {
503 controls.enable_cursor_blink.term_input_into(buf);
504 } else {
505 controls.disable_cursor_blink.term_input_into(buf);
506 }
507 }
508 if self.application_cursor_keys_enabled {
509 controls.enable_application_cursor_keys.term_input_into(buf);
510 }
511 if self.application_keypad_mode_enabled {
512 controls.enable_application_keypad_mode.term_input_into(buf);
513 }
514 if self.report_focus {
515 controls.enable_report_focus.term_input_into(buf);
516 }
517 if self.in_paste_mode {
518 controls.enable_paste_mode.term_input_into(buf);
519 }
520 if self.insert_mode {
521 controls.enable_insert_mode.term_input_into(buf);
522 }
523 for (idx, mode) in MOUSE_MODES.iter().enumerate() {
524 if self.mouse_modes[idx] {
525 ControlCodes::dec_private_modes_set(&[*mode]).term_input_into(buf);
526 }
527 }
528
529 let mut functional_color_idx = 0;
532 while functional_color_idx < self.functional_colors.len() {
533 if let Some(color_spec) = &self.functional_colors[functional_color_idx] {
534 let start_idx = functional_color_idx;
535 let mut color_specs = vec![color_spec.as_slice()];
536
537 functional_color_idx += 1;
538 while functional_color_idx < self.functional_colors.len() {
539 if let Some(s) = &self.functional_colors[functional_color_idx] {
540 color_specs.push(s.as_slice());
541 } else {
542 break;
543 }
544 functional_color_idx += 1;
545 }
546
547 ControlCodes::set_functional_color(start_idx, color_specs).term_input_into(buf);
548 }
549
550 functional_color_idx += 1;
551 }
552 }
553
554 fn set_functional_color<'a, I>(&mut self, mut idx: usize, mut params_iter: I)
557 where
558 I: Iterator<Item = &'a &'a [u8]>,
559 {
560 while let Some(color_spec) = params_iter.next() {
561 if idx >= self.functional_colors.len() {
562 return;
563 }
564
565 if *color_spec != [b'?'] {
566 self.functional_colors[idx] = Some(Vec::from(*color_spec));
567 }
568
569 idx += 1;
570 }
571 }
572
573 fn set_title(&mut self, title: SmallVec<[u8; 8]>) {
574 if let Some(top) = self.title_stack.last_mut() {
575 *top = title;
576 } else {
577 self.title_stack.push(title);
578 }
579 }
580
581 fn set_icon_name(&mut self, icon_name: SmallVec<[u8; 8]>) {
582 if let Some(top) = self.icon_name_stack.last_mut() {
583 *top = icon_name;
584 } else {
585 self.icon_name_stack.push(icon_name);
586 }
587 }
588
589 fn write_char_at_cursor(&mut self, cell: Cell) {
590 let insert_mode = self.insert_mode;
591 let screen = self.screen_mut();
592 screen.snap_to_bottom();
593
594 if insert_mode {
605 let width = screen.size.width;
606 let col = screen.cursor.col;
607 if col < width {
608 if let Some(l) = screen.get_line_mut() {
609 l.insert_character(width, col, cell.width() as usize);
610 }
611 }
612 }
613
614 if let Err(e) = screen.write_at_cursor(cell) {
615 warn!(self.logger, "writing char at cursor: {:?}", e);
616 }
617 }
618
619 fn add_modifier_char(&mut self, c: char) {
628 let screen = self.screen_mut();
629 let width = screen.size.width;
630 let Some(mut col) = screen.cursor.col.checked_sub(1) else {
631 return;
632 };
633
634 let Some(line) = screen.get_line_mut() else {
635 return;
636 };
637
638 while line.get_cell(width, col).is_some_and(|cell| cell.is_wide_padding()) {
639 match col.checked_sub(1) {
640 Some(prev) => col = prev,
641 None => return,
642 }
643 }
644
645 match line.get_cell_mut(width, col) {
646 Some(cell) if !cell.is_empty() => cell.add_char(c),
648 _ => {}
649 }
650 }
651}
652
653enum ScreenMode {
655 Scrollback,
656 Alt,
657}
658
659impl vte::Perform for State {
660 fn print(&mut self, c: char) {
661 trace!(self.logger, "print: {}", c);
662
663 match UnicodeWidthChar::width(c) {
664 None => {
668 warn!(self.logger, "print: dropping control char {:?}", c);
669 return;
670 }
671 Some(0) => {
674 self.add_modifier_char(c);
675 return;
676 }
677 Some(_) => {}
678 }
679
680 self.last_print_char = Some(c);
681 let attrs = self.cursor_attrs.clone();
682 self.write_char_at_cursor(Cell::new(c, attrs));
683 }
684
685 fn execute(&mut self, byte: u8) {
686 self.last_print_char = None;
687 trace!(self.logger, "execute: byte {}", byte);
688 match byte {
689 b'\n' => {
690 let screen = self.screen_mut();
691 let (scroll_top, scroll_bottom) =
692 screen.scroll_region(false).as_region(&screen.size).row_bounds();
693 let within_scroll =
694 scroll_top <= screen.cursor.row && screen.cursor.row < scroll_bottom;
695 screen.cursor.row += 1;
696 if within_scroll {
697 if screen.cursor.row >= scroll_bottom {
698 screen.scroll_down(1);
699 screen.cursor.row -= 1;
700 }
701 } else {
702 screen.clamp();
703 }
704 }
705 b'\r' => self.screen_mut().cursor.col = 0,
706 b'\t' => {
707 let mut col = self.screen().cursor.col;
708 col += 1;
709 while col < self.tabstops.len() && !self.tabstops.get(col).is_some_and(|b| *b) {
710 col += 1;
711 }
712
713 let screen = self.screen_mut();
714 screen.cursor.col = col;
715 screen.clamp();
716 }
717 b'\x08' => {
718 let screen = self.screen_mut();
720 screen.cursor.col = screen.cursor.col.saturating_sub(1);
721 }
722 b'\x07' => {}
724 _ => {
725 warn!(self.logger, "execute: unhandled byte {}", byte);
726 }
727 }
728 }
729
730 fn hook(&mut self, _params: &vte::Params, intermediates: &[u8], ignore: bool, action: char) {
731 self.last_print_char = None;
732 debug!(
733 self.logger,
734 "unhandled hook{}: {:?} {}",
735 if ignore { " (ignored)" } else { "" },
736 intermediates,
737 action
738 );
739 }
740
741 fn put(&mut self, byte: u8) {
742 trace!(self.logger, "unhandled put: {}", byte);
743 self.last_print_char = None;
744 }
745
746 fn unhook(&mut self) {
747 debug!(self.logger, "unhandled unhook");
748 self.last_print_char = None;
749 }
750
751 #[rustfmt::skip]
760 fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
761 trace!(self.logger, "osc_dispatch: {:?}", params);
762 self.last_print_char = None;
763
764 let mut params_iter = params.iter();
765 match params_iter.next() {
766 Some([b'0']) => if let Some(title) = params_iter.next() {
768 let title: SmallVec<[u8; 8]> = title.to_vec().into();
769 self.set_title(title.clone());
770 self.set_icon_name(title);
771 } else {
772 warn!(self.logger, "OSC 0 with no title param");
773 },
774 Some([b'1']) => if let Some(icon_name) = params_iter.next() {
775 let icon_name: SmallVec<[u8; 8]> = icon_name.to_vec().into();
776 self.set_icon_name(icon_name);
777 } else {
778 warn!(self.logger, "OSC 1 with no icon_name param");
779 },
780 Some([b'2']) => if let Some(title) = params_iter.next() {
781 let title: SmallVec<[u8; 8]> = title.to_vec().into();
782 self.set_title(title);
783 } else {
784 warn!(self.logger, "OSC 2 with no title param");
785 },
786
787 Some([b'4']) => while let (Some(idx), Some(color_spec)) = (params_iter.next(), params_iter.next()) {
789 if *color_spec == [b'?'] {
790 continue;
794 }
795
796 match std::str::from_utf8(idx) {
797 Ok(s) => match s.parse::<usize>() {
798 Ok(i) => {
799 self.palette_overrides.insert(i, color_spec.to_vec());
800 },
801 Err(e) => warn!(self.logger, "OSC 4: idx is an invalid number '{}': {}", s, e),
802 },
803 Err(e) => warn!(self.logger, "OSC 4: invalid idx '{:?}': {}", idx, e),
804 }
805 },
806 Some([b'1', b'0', b'4']) => while let Some(idx) = params_iter.next() {
807 match std::str::from_utf8(idx) {
808 Ok(s) => match s.parse::<usize>() {
809 Ok(i) => {
810 self.palette_overrides.remove(&i);
811 },
812 Err(e) => warn!(self.logger, "OSC 104: idx is an invalid number '{}': {}", s, e),
813 },
814 Err(e) => warn!(self.logger, "OSC 104: invalid idx '{:?}': {}", idx, e),
815 }
816 },
817
818 Some([b'7']) => if let (Some(host), Some(dir)) = (params_iter.next(), params_iter.next()) {
820 self.working_dir = Some(WorkingDir {
821 host: host.to_vec().into(),
822 dir: dir.to_vec().into(),
823 });
824 } else {
825 warn!(self.logger, "OSC 7 with fewer than 2 params");
826 },
827
828 Some([b'8']) => if let (Some(params), Some(url)) = (params_iter.next(), params_iter.next()) {
830 if params.is_empty() && url.is_empty() {
831 self.cursor_attrs.link_target = None;
832 } else {
833 self.cursor_attrs.link_target = Some(LinkTarget {
834 params: SmallVec::from_slice(params),
835 url: SmallVec::from_slice(url),
836 });
837 }
838 } else {
839 self.cursor_attrs.link_target = None;
840 },
841
842 Some([b'1', x]) if b'0' <= *x && *x <= b'9' =>
844 self.set_functional_color((*x - b'0') as usize, params_iter),
845
846 Some([b'5', b'2']) => debug!(self.logger, "ignoring OSC 52 (clipboard)"),
847 Some([b'9']) => debug!(self.logger, "ignoring OSC 9 (desktop notification)"),
848 Some([b'7', b'7', b'7']) => debug!(self.logger, "ignoring OSC 777"),
849 Some([b'1', b'3', b'3']) => debug!(self.logger, "ignoring OSC 133 (iterm2 marks)"),
850 Some([b'3', b'0', b'0', b'8']) => debug!(self.logger, "ignoring OSC 3008 (systemd context signaling)"),
851
852 _ => warn!(self.logger, "unhandled 'OSC {:?} {}'", params, if bell_terminated {
853 "BEL"
854 } else {
855 "ST"
856 }),
857 }
858 }
859
860 #[rustfmt::skip]
867 fn csi_dispatch(
868 &mut self,
869 params: &vte::Params,
870 intermediates: &[u8],
871 ignore: bool,
872 action: char,
873 ) {
874 if ignore {
875 warn!(self.logger, "malformed CSI seq");
876 return;
877 }
878 if tracing::enabled!(tracing::Level::TRACE) {
879 trace!(self.logger, "csi_dispatch: intermediates={:?} params={:?} {}",
880 intermediates, params.iter().collect::<Vec<_>>(), action);
881 }
882
883 let mut params_iter = params.iter();
884
885 if action != 'b' || !intermediates.is_empty() {
886 self.last_print_char = None;
887 }
888
889 match action {
890 'A' => {
892 let n = param_or(&mut params_iter, 1) as usize;
893 let screen = self.screen_mut();
894 screen.cursor.row = screen.cursor.row.saturating_sub(n);
895 screen.clamp();
896 }
897 'B' => {
899 let n = param_or(&mut params_iter, 1) as usize;
900 let screen = self.screen_mut();
901 screen.cursor.row += n;
902 screen.clamp();
903 }
904 'C' => {
906 let n = param_or(&mut params_iter, 1) as usize;
907 let screen = self.screen_mut();
908 screen.cursor.col += n;
909 screen.clamp();
910 }
911 'D' => {
913 let n = param_or(&mut params_iter, 1) as usize;
914 let screen = self.screen_mut();
915 screen.cursor.col = screen.cursor.col.saturating_sub(n);
916 screen.clamp();
917 }
918 'E' => {
920 let n = param_or(&mut params_iter, 1) as usize;
921 let screen = self.screen_mut();
922 screen.cursor.row += n;
923 screen.cursor.col = 0;
924 screen.clamp();
925 }
926 'F' => {
928 let n = param_or(&mut params_iter, 1) as usize;
929 let screen = self.screen_mut();
930 screen.cursor.row = screen.cursor.row.saturating_sub(n);
931 screen.cursor.col = 0;
932 screen.clamp();
933 }
934 '`' | 'G' => {
937 let n = param_or(&mut params_iter, 1) as usize;
938 let n = n.saturating_sub(1); let screen = self.screen_mut();
941 screen.cursor.col = n;
942 screen.clamp();
943 }
944 'f' | 'H' => {
947 let row = param_or(&mut params_iter, 1) as usize;
949 let col = param_or(&mut params_iter, 1) as usize;
950 let screen = self.screen_mut();
951 screen.set_cursor(term::Pos { row, col });
952 screen.clamp();
953 }
954 'J' => while let Some(code) = params_iter.next() {
956 match code {
957 [] | [0] => self.screen_mut().erase_to_end(),
958 [1] => self.screen_mut().erase_from_start(),
959 [2] => self.screen_mut().erase(false),
960 [3] => self.screen_mut().erase(true),
961 _ => warn!(self.logger, "unhandled 'CSI {:?} J'", code),
962 }
963 }
964 'K' => while let Some(code) = params_iter.next() {
966 match code {
967 [] | [0] => {
968 let screen = self.screen_mut();
969 let col = screen.cursor.col;
970 if let Some(l) = screen.get_line_mut() {
971 l.erase(line::Section::ToEnd(col));
972 }
973 }
974 [1] => {
975 let screen = self.screen_mut();
976 let col = screen.cursor.col;
977 if let Some(l) = screen.get_line_mut() {
978 l.erase(line::Section::StartTo(col));
979 }
980 }
981 [2] => if let Some(l) = self.screen_mut().get_line_mut() {
982 l.erase(line::Section::Whole);
983 }
984 _ => warn!(self.logger, "unhandled 'CSI {:?} K'", code),
985 }
986 }
987 'L' => {
989 let n = param_or(&mut params_iter, 1) as usize;
990 self.screen_mut().insert_lines(n);
991 }
992 'M' => {
994 let n = param_or(&mut params_iter, 1) as usize;
995 self.screen_mut().delete_lines(n);
996 }
997 'S' => {
999 let n = param_or(&mut params_iter, 1) as usize;
1000 self.screen_mut().scroll_up(n as usize);
1001 }
1002 'W' => {
1004 let code = param_or(&mut params_iter, 0) as usize;
1005 match code {
1006 0 => {
1007 let col = self.screen().cursor.col;
1008 self.tabstops.set(col, true);
1009 },
1010 2 => {
1011 let col = self.screen().cursor.col;
1012 self.tabstops.set(col, false);
1013 }
1014 5 => {
1015 self.tabstops.fill(false);
1016 }
1017 _ => warn!(self.logger, "unhandled 'CSI {:?} W'", code),
1018 }
1019 }
1020 'Z' if intermediates.is_empty() => {
1022 let n = param_or(&mut params_iter, 1) as usize;
1023 let mut col = self.screen().cursor.col;
1024 for _ in 0..n {
1025 if col == 0 {
1026 break;
1027 }
1028 col -= 1;
1029 while col > 0 && !self.tabstops.get(col).is_some_and(|b| *b) {
1030 col -= 1;
1031 }
1032 }
1033
1034 let screen = self.screen_mut();
1035 screen.cursor.col = col;
1036 screen.clamp();
1037 }
1038 'T' => {
1040 let n = param_or(&mut params_iter, 1) as usize;
1041 self.screen_mut().scroll_down(n as usize);
1042 }
1043
1044 '@' => {
1046 let n = param_or(&mut params_iter, 1) as usize;
1047
1048 let screen = self.screen_mut();
1049 let width = screen.size.width;
1050 let col = screen.cursor.col;
1051 if let Some(l) = screen.get_line_mut() {
1052 l.insert_character(width, col, n);
1053 }
1054 }
1055 'P' => {
1057 let n = param_or(&mut params_iter, 1) as usize;
1058
1059 let attrs = self.cursor_attrs.clone();
1060
1061 let screen = self.screen_mut();
1062 let width = screen.size.width;
1063 let col = screen.cursor.col;
1064 if let Some(l) = screen.get_line_mut() {
1065 l.delete_character(width, col, &attrs, n);
1066 }
1067 }
1068 'X' => {
1070 let n = param_or(&mut params_iter, 1) as usize;
1071
1072 let attrs = self.cursor_attrs.clone();
1073
1074 let screen = self.screen_mut();
1075 let width = screen.size.width;
1076 let col = screen.cursor.col;
1077 if let Some(l) = screen.get_line_mut() {
1078 l.erase_character(width, col, &attrs, n);
1079 }
1080 }
1081 'b' if intermediates.is_empty() => if let Some(c) = self.last_print_char {
1083 let n = param_or(&mut params_iter, 1) as usize;
1084
1085 let cell = Cell::new(c, self.cursor_attrs.clone());
1086 for _ in 0..n {
1087 self.write_char_at_cursor(cell.clone());
1088 }
1089 }
1090 'c' => debug!(self.logger, "CSI ... c - device attribute query"),
1091 'd' => {
1093 let row = param_or(&mut params_iter, 1) as usize;
1094 let col = self.screen().cursor.col + 1;
1095 let screen = self.screen_mut();
1096 screen.set_cursor(term::Pos { row, col });
1097 screen.clamp();
1098 }
1099
1100 's' => {
1102 let screen = self.screen_mut();
1103 let cursor = screen.cursor.clone();
1104 screen.saved_cursor.pos = cursor;
1105 }
1106 't' => while let Some(code) = params_iter.next() {
1108 match code {
1109 [] | [0] => debug!(self.logger, "CSI 0 t - ignoring"),
1110 [14, ..] => debug!(self.logger, "CSI 14 t - pixel size query"),
1111 [16, ..] => debug!(self.logger, "CSI 16 t - cell size query"),
1112 [18, ..] => debug!(self.logger, "CSI 18 t - term size query"),
1113 [19, ..] => debug!(self.logger, "CSI 19 t - display size query"),
1114 [22] => {
1115 let code = param_or(&mut params_iter, 0) as usize;
1116 if (code == 0 || code == 1) && self.icon_name_stack.len() < MAX_TITLE_STACK_DEPTH {
1117 if let Some(icon_name) = self.icon_name_stack.last().cloned() {
1118 self.icon_name_stack.push(icon_name);
1119 } else {
1120 self.icon_name_stack.push(SmallVec::new());
1121 }
1122 }
1123
1124 if (code == 0 || code == 2) && self.title_stack.len() < MAX_TITLE_STACK_DEPTH {
1125 if let Some(title) = self.title_stack.last().cloned() {
1126 self.title_stack.push(title);
1127 } else {
1128 self.title_stack.push(SmallVec::new());
1129 }
1130 }
1131 }
1132 [23] => {
1133 let code = param_or(&mut params_iter, 0) as usize;
1134 if code == 0 || code == 1 {
1135 self.icon_name_stack.pop();
1136 }
1137
1138 if code == 0 || code == 2 {
1139 self.title_stack.pop();
1140 }
1141 }
1142 _ => warn!(self.logger, "unhandled CSI ... {:?} t", code),
1143 }
1144 }
1145 'u' => {
1147 let screen = self.screen_mut();
1148 screen.cursor = screen.saved_cursor.pos;
1149 screen.clamp();
1150 }
1151
1152 'g' => {
1154 let code = param_or(&mut params_iter, 0) as usize;
1155 match code {
1156 0 => {
1157 let col = self.screen().cursor.col;
1158 self.tabstops.set(col, false);
1159 },
1160 3 => {
1161 self.tabstops.fill(false);
1162 }
1163 _ => warn!(self.logger, "unhandled 'CSI {:?} g'", code),
1164 }
1165 }
1166
1167 'h' => match intermediates {
1168 [] => while let Some(code) = params_iter.next() {
1169 match code {
1170 [4] => self.insert_mode = true,
1171 _ => {
1172 warn!(
1173 self.logger,
1174 "Unhandled CSI h command: CSI {:?} {:?} h",
1175 intermediates,
1176 params.iter().collect::<Vec<&[u16]>>()
1177 );
1178 }
1179 }
1180 }
1181 [b'?'] => while let Some(code) = params_iter.next() {
1182 match code {
1183 [1] => self.application_cursor_keys_enabled = true,
1184 [3] => {},
1187 [4] => {},
1190 [6] => self.screen_mut().set_origin_mode(OriginMode::ScrollRegion),
1191 [12] => self.cursor_blinking = Some(true),
1192 [25] => self.cursor_hidden = false,
1193 [1004] => self.report_focus = true,
1194 [1049] => {
1196 self.altscreen = Screen::alt(self.altscreen.size);
1199 self.screen_mode = ScreenMode::Alt;
1200 }
1201 [2004] => self.in_paste_mode = true,
1202 [2026] => {},
1205
1206 _ => {
1207 if let Some(idx) = mouse_mode_idx(code) {
1208 self.mouse_modes[idx] = true;
1209 } else {
1210 warn!(
1211 self.logger,
1212 "Unhandled CSI h command: CSI {:?} {:?} h",
1213 intermediates,
1214 params.iter().collect::<Vec<&[u16]>>()
1215 );
1216 }
1217 }
1218 }
1219 }
1220 _ => warn!(
1221 self.logger,
1222 "Unhandled CSI h command: CSI {:?} {:?} h",
1223 intermediates,
1224 params.iter().collect::<Vec<&[u16]>>()
1225 ),
1226 }
1227 'l' => match intermediates {
1228 [] => while let Some(code) = params_iter.next() {
1229 match code {
1230 [4] => self.insert_mode = false,
1231 _ => {
1232 warn!(
1233 self.logger,
1234 "Unhandled CSI l command: CSI {:?} {:?} l",
1235 intermediates,
1236 params.iter().collect::<Vec<&[u16]>>()
1237 );
1238 }
1239 }
1240 }
1241 [b'?'] => while let Some(code) = params_iter.next() {
1242 match code {
1243 [1] => self.application_cursor_keys_enabled = false,
1244 [3] => {},
1249 [4] => {},
1252 [6] => self.screen_mut().set_origin_mode(OriginMode::Term),
1253 [12] => self.cursor_blinking = Some(false),
1254 [25] => self.cursor_hidden = true,
1255 [1004] => self.report_focus = false,
1256 [1049] => self.screen_mode = ScreenMode::Scrollback,
1257 [2004] => self.in_paste_mode = false,
1258 [2026] => {},
1261 _ => {
1262 if let Some(idx) = mouse_mode_idx(code) {
1263 self.mouse_modes[idx] = false;
1264 } else {
1265 warn!(
1266 self.logger,
1267 "Unhandled CSI l command: CSI {:?} {:?} l",
1268 intermediates,
1269 params.iter().collect::<Vec<&[u16]>>()
1270 );
1271 }
1272 }
1273 }
1274 }
1275 _ => warn!(
1276 self.logger,
1277 "Unhandled CSI l command: CSI {:?} {:?} l",
1278 intermediates,
1279 params.iter().collect::<Vec<&[u16]>>()
1280 ),
1281 },
1282 'n' => while let Some(param) = params_iter.next() {
1284 match param {
1285 [6] => debug!(self.logger, "ignoring DSR (CSI 6 n), that's the real terminal's job"),
1294 _ => {}
1295 }
1296 },
1297
1298 'm' => while let Some(param) = params_iter.next() {
1300 match param {
1301 [] | [0] => self.cursor_attrs = term::Attrs::default(),
1302
1303 [4] => self.cursor_attrs.underline = Some(UnderlineStyle::Single),
1314 [21] => self.cursor_attrs.underline = Some(UnderlineStyle::Double),
1315 [24] => self.cursor_attrs.underline = None,
1316
1317 [1] => self.cursor_attrs.font_weight = Some(FontWeight::Bold),
1319 [2] => self.cursor_attrs.font_weight = Some(FontWeight::Faint),
1320 [22] => self.cursor_attrs.font_weight = None,
1321
1322 [3] => self.cursor_attrs.italic = true,
1324 [23] => self.cursor_attrs.italic = false,
1325
1326 [7] => self.cursor_attrs.inverse = true,
1328 [27] => self.cursor_attrs.inverse = false,
1329
1330 [5] => self.cursor_attrs.blink = Some(BlinkStyle::Slow),
1332 [6] => self.cursor_attrs.blink = Some(BlinkStyle::Rapid),
1333 [25] => self.cursor_attrs.blink = None,
1334
1335 [8] => self.cursor_attrs.conceal = true,
1337 [28] => self.cursor_attrs.conceal = false,
1338
1339 [9] => self.cursor_attrs.strikethrough = true,
1341 [29] => self.cursor_attrs.strikethrough = false,
1342
1343 [51] => self.cursor_attrs.framed = Some(FrameStyle::Frame),
1345 [52] => self.cursor_attrs.framed = Some(FrameStyle::Circle),
1346 [54] => self.cursor_attrs.framed = None,
1347
1348 [53] => self.cursor_attrs.overline = true,
1350 [55] => self.cursor_attrs.overline = false,
1351
1352 [59] => self.cursor_attrs.underline_color = term::Color::Default,
1354 param if !param.is_empty() && param[0] == 58 => {
1355 match parse_extended_color(param, &mut params_iter) {
1356 Some(color) => self.cursor_attrs.underline_color = color,
1357 None => warn!(self.logger, "unhandled incomplete 'CSI 58 ... m'"),
1358 }
1359 }
1360
1361 [49] => self.cursor_attrs.bgcolor = term::Color::Default,
1363 [n] if 40 <= *n && *n < 48 => match (*n - 40).try_into() {
1364 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
1365 Err(e) => warn!(self.logger, "out of bounds bgcolor idx (1): {:?}", e),
1366 }
1367 [n] if 100 <= *n && *n < 108 => match (*n - 92).try_into() {
1368 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
1369 Err(e) => warn!(self.logger, "out of bounds bgcolor idx (2): {:?}", e),
1370 }
1371 param if !param.is_empty() && param[0] == 48 => {
1372 match parse_extended_color(param, &mut params_iter) {
1373 Some(color) => self.cursor_attrs.bgcolor = color,
1374 None => warn!(self.logger, "unhandled incomplete 'CSI 48 ... m'"),
1375 }
1376 }
1377
1378 [39] => self.cursor_attrs.fgcolor = term::Color::Default,
1380 [n] if 30 <= *n && *n < 38 => match (*n - 30).try_into() {
1381 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
1382 Err(e) => warn!(self.logger, "out of bounds fgcolor idx (1): {:?}", e),
1383 }
1384 [n] if 90 <= *n && *n < 98 => match (*n - 82).try_into() {
1385 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
1386 Err(e) => warn!(self.logger, "out of bounds fgcolor idx (2): {:?}", e),
1387 }
1388 param if !param.is_empty() && param[0] == 38 => {
1389 match parse_extended_color(param, &mut params_iter) {
1390 Some(color) => self.cursor_attrs.fgcolor = color,
1391 None => warn!(self.logger, "unhandled incomplete 'CSI 38 ... m'"),
1392 }
1393 }
1394
1395 _ => warn!(self.logger, "unhandled 'CSI {:?} m'", param),
1396 }
1397 }
1398 'p' => match intermediates {
1399 [b'!'] => {
1401 self.tabstops.fill(false);
1402 let width = self.screen().size.width;
1403 self.fill_tabstops(0, width);
1404 self.cursor_style = term::CursorStyle::Default;
1405 self.cursor_attrs = term::Attrs::default();
1406 self.cursor_blinking = None;
1407 self.insert_mode = false;
1408
1409 warn!(self.logger, "DECSTR only partially handled");
1410 }
1411 [b'?', b'$'] => {
1413 debug!(self.logger, "ignoring DECRQM query: params={:?}", params.iter().collect::<Vec<_>>());
1425 }
1426 _ => warn!(
1427 self.logger,
1428 "Unhandled CSI p command: CSI {:?} {:?} p",
1429 intermediates,
1430 params.iter().collect::<Vec<&[u16]>>()
1431 ),
1432 },
1433 'q' if intermediates == [b' '] => {
1435 let code = param_or(&mut params_iter, 0) as usize;
1436 match term::CursorStyle::try_from(code) {
1437 Ok(style) => self.cursor_style = style,
1438 Err(e) => warn!(self.logger, "parsing cursor style: {:?}", e),
1439 }
1440 },
1441 'r' => {
1443 let top = maybe_param(&mut params_iter);
1444 let bottom = maybe_param(&mut params_iter);
1445
1446 let screen = self.screen_mut();
1447 screen.set_scroll_region(match (top, bottom) {
1448 (None, None) => term::ScrollRegion::TrackSize,
1449 (Some(t), None) => term::ScrollRegion::Window {
1450 top: t.saturating_sub(1) as usize,
1451 bottom: screen.size.height,
1452 },
1453 (None, Some(b)) => term::ScrollRegion::Window {
1454 top: 0,
1455 bottom: b as usize,
1456 },
1457 (Some(t), Some(b)) => term::ScrollRegion::Window {
1458 top: t.saturating_sub(1) as usize,
1459 bottom: b as usize,
1460 }
1461 });
1462 }
1463
1464 _ => {
1465 warn!(self.logger, "unhandled action {}", action);
1466 }
1467 }
1468 }
1469
1470 fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
1471 if ignore {
1472 warn!(self.logger, "malformed ESC seq");
1473 return;
1474 }
1475 trace!(self.logger, "esc_dispatch: {}", byte);
1476 self.last_print_char = None;
1477
1478 match (intermediates, byte) {
1479 ([], b'7') => {
1481 let attrs = self.cursor_attrs.clone();
1482 let screen = self.screen_mut();
1483 let pos = screen.cursor.clone();
1484 screen.saved_cursor = SavedCursor { pos, attrs };
1485 }
1486 ([], b'8') => {
1488 let screen = self.screen_mut();
1489 screen.cursor = screen.saved_cursor.pos;
1490 self.cursor_attrs = screen.saved_cursor.attrs.clone();
1491 }
1492 ([], b'H') => {
1494 let col = self.screen().cursor.col;
1495 self.tabstops.set(col, true);
1496 }
1497 ([], b'M') => {
1499 let screen = self.screen_mut();
1500 let (scroll_top, _) =
1501 screen.scroll_region(false).as_region(&screen.size).row_bounds();
1502
1503 if screen.cursor.row == scroll_top {
1504 screen.insert_lines(1);
1505 } else if screen.cursor.row > 0 {
1506 screen.cursor.row -= 1;
1507 }
1508 }
1509 ([], b'c') => {
1511 self.tabstops.fill(false);
1512 let width = self.screen().size.width;
1513 self.fill_tabstops(0, width);
1514 self.cursor_style = term::CursorStyle::Default;
1515 self.cursor_attrs = term::Attrs::default();
1516 self.cursor_blinking = None;
1517 self.insert_mode = false;
1518
1519 warn!(self.logger, "RIS only partially handled");
1520 }
1521
1522 ([], b'=') => self.application_keypad_mode_enabled = true,
1524 ([], b'>') => self.application_keypad_mode_enabled = false,
1525
1526 ([b'(' | b')' | b'*' | b'+'], b'B' | b'A') => {}
1529
1530 ([], 92) => {}
1533
1534 _ => warn!(self.logger, "unhandled ESC seq ({:?}, {})", intermediates, byte),
1535 }
1536 }
1537
1538 fn terminated(&self) -> bool {
1539 false
1540 }
1541}
1542
1543fn param_or<'params>(params: &mut vte::ParamsIter<'params>, default: u16) -> u16 {
1544 maybe_param(params).unwrap_or(default)
1545}
1546
1547fn maybe_param<'params>(params: &mut vte::ParamsIter<'params>) -> Option<u16> {
1548 match params.next() {
1549 Some([0]) => None,
1550 Some([p]) => Some(*p),
1551 _ => None,
1552 }
1553}
1554
1555fn parse_extended_color<'params>(
1556 first_param: &[u16],
1557 params_iter: &mut vte::ParamsIter<'params>,
1558) -> Option<term::Color> {
1559 if first_param.len() > 1 {
1560 match first_param[1] {
1563 5 => {
1564 if first_param.len() >= 3 {
1565 let idx = first_param[2].try_into().ok()?;
1566 Some(term::Color::Idx(idx))
1567 } else {
1568 None
1569 }
1570 }
1571 2 => {
1572 if first_param.len() == 5 {
1573 let r = first_param[2].try_into().ok()?;
1574 let g = first_param[3].try_into().ok()?;
1575 let b = first_param[4].try_into().ok()?;
1576 Some(term::Color::Rgb(r, g, b))
1577 } else if first_param.len() >= 6 {
1578 let r = first_param[3].try_into().ok()?;
1581 let g = first_param[4].try_into().ok()?;
1582 let b = first_param[5].try_into().ok()?;
1583 Some(term::Color::Rgb(r, g, b))
1584 } else {
1585 None
1586 }
1587 }
1588 _ => None,
1589 }
1590 } else {
1591 match params_iter.next() {
1593 Some([5]) => {
1594 let n = param_or(params_iter, 0);
1595 let idx = n.try_into().ok()?;
1596 Some(term::Color::Idx(idx))
1597 }
1598 Some([2]) => {
1599 let r = param_or(params_iter, 0);
1600 let g = param_or(params_iter, 0);
1601 let b = param_or(params_iter, 0);
1602 let r = r.try_into().ok()?;
1603 let g = g.try_into().ok()?;
1604 let b = b.try_into().ok()?;
1605 Some(term::Color::Rgb(r, g, b))
1606 }
1607 _ => None,
1608 }
1609 }
1610}
1611
1612const NONE_VEC: Option<Vec<u8>> = None;