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;
28
29#[macro_use]
30mod visibility;
31
32#[macro_use]
33mod log;
34
35mod altscreen;
36mod cell;
37mod line;
38mod screen;
39mod scrollback;
40
41#[cfg(not(feature = "unstable-internal-test"))]
42mod term;
43
44#[cfg(feature = "unstable-internal-test")]
45pub mod term;
46
47const MAX_TITLE_STACK_DEPTH: usize = 64;
48
49pub struct Term {
51 parser: vte::Parser,
52 state: State,
53 logger: log::Context,
54}
55
56impl Term {
57 pub fn new(scrollback_lines: usize, size: Size) -> Self {
66 Term {
67 parser: vte::Parser::new(),
68 state: State::new(scrollback_lines, size),
69 logger: log::Context::None,
70 }
71 }
72
73 pub fn tag(&mut self, tag: String) {
77 let logger = log::Context::Tag(tag);
78 self.logger = logger.clone();
79 self.state.set_logger(logger);
80 }
81
82 pub fn size(&self) -> Size {
84 self.state.screen().size
85 }
86
87 pub fn resize(&mut self, size: Size) {
92 if size.height > self.scrollback_lines() {
93 self.set_scrollback_lines(size.height);
94 }
95
96 self.state.resize(size);
97 }
98
99 pub fn scrollback_lines(&self) -> usize {
101 self.state.scrollback.scrollback_lines().expect("scrollback screen to have lines")
102 }
103
104 pub fn set_scrollback_lines(&mut self, scrollback_lines: usize) {
112 self.state.scrollback.set_scrollback_lines(scrollback_lines);
113 }
114
115 pub fn process(&mut self, buf: &[u8]) {
118 self.parser.advance(&mut self.state, buf);
119 }
120
121 pub fn contents(&self, dump_region: ContentRegion) -> Vec<u8> {
126 let mut buf = vec![];
127
128 term::control_codes().end_link.term_input_into(&mut buf);
132
133 term::control_codes().clear_attrs.term_input_into(&mut buf);
134 term::ControlCodes::cursor_position(1, 1).term_input_into(&mut buf);
135 term::control_codes().clear_screen.term_input_into(&mut buf);
136 self.state.dump_contents_into(&mut buf, dump_region);
137
138 buf
139 }
140}
141
142#[derive(Debug, Eq, PartialEq, Clone)]
144pub enum ContentRegion {
145 All,
147 Screen,
149 BottomLines(usize),
151}
152
153impl std::fmt::Display for Term {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 self.state.fmt(f)
156 }
157}
158
159#[derive(Debug, Clone, Copy, Eq, PartialEq)]
161pub struct Size {
162 pub width: usize,
163 pub height: usize,
164}
165
166struct State {
168 scrollback: Screen,
170 altscreen: Screen,
172 screen_mode: ScreenMode,
174 last_print_char: Option<char>,
177 cursor_attrs: term::Attrs,
182 cursor_style: term::CursorStyle,
185 title_stack: Vec<SmallVec<[u8; 8]>>,
187 icon_name_stack: Vec<SmallVec<[u8; 8]>>,
189 working_dir: Option<WorkingDir>,
192 palette_overrides: BTreeMap<usize, Vec<u8>>,
196 functional_colors: [Option<Vec<u8>>; 10],
199 cursor_hidden: bool,
202 cursor_blinking: Option<bool>,
204 application_keypad_mode_enabled: bool,
207 report_focus: bool,
214 in_paste_mode: bool,
216 insert_mode: bool,
218 tabstops: BitVec,
222 logger: log::Context,
223}
224
225struct WorkingDir {
226 host: SmallVec<[u8; 8]>,
227 dir: SmallVec<[u8; 8]>,
228}
229
230impl std::fmt::Display for State {
231 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
232 match self.screen_mode {
233 ScreenMode::Scrollback => {
234 writeln!(f, "Screen Mode: Scrollback")?;
235 write!(f, "{}", self.scrollback)?;
236 }
237 ScreenMode::Alt => {
238 writeln!(f, "Screen Mode: AltScreen")?;
239 write!(f, "{}", self.altscreen)?;
240 }
241 }
242
243 Ok(())
244 }
245}
246
247impl State {
248 fn new(scrollback_lines: usize, size: Size) -> Self {
249 let mut st = State {
250 scrollback: Screen::scrollback(scrollback_lines, size),
251 altscreen: Screen::alt(size),
252 screen_mode: ScreenMode::Scrollback,
253 cursor_attrs: term::Attrs::default(),
254 cursor_style: term::CursorStyle::Default,
255 title_stack: vec![],
256 icon_name_stack: vec![],
257 working_dir: None,
258 palette_overrides: BTreeMap::new(),
259 functional_colors: [NONE_VEC; 10],
260 cursor_hidden: false,
261 cursor_blinking: None,
262 application_keypad_mode_enabled: false,
263 report_focus: false,
264 in_paste_mode: false,
265 insert_mode: false,
266 tabstops: bitvec![0; size.width],
267 last_print_char: None,
268 logger: log::Context::None,
269 };
270 st.fill_tabstops(0, size.width);
271 st
272 }
273
274 fn set_logger(&mut self, logger: log::Context) {
275 self.scrollback.set_logger(logger.clone());
276 self.altscreen.set_logger(logger.clone());
277 self.logger = logger;
278 }
279
280 fn screen_mut(&mut self) -> &mut Screen {
281 match self.screen_mode {
282 ScreenMode::Scrollback => &mut self.scrollback,
283 ScreenMode::Alt => &mut self.altscreen,
284 }
285 }
286
287 fn screen(&self) -> &Screen {
288 match self.screen_mode {
289 ScreenMode::Scrollback => &self.scrollback,
290 ScreenMode::Alt => &self.altscreen,
291 }
292 }
293
294 fn resize(&mut self, size: Size) {
295 let orig_len = self.tabstops.len();
296 self.tabstops.resize(size.width, false);
297 if size.width > orig_len {
298 self.fill_tabstops(orig_len, size.width);
299 }
300
301 self.scrollback.resize(size);
302 self.altscreen.resize(size);
303 }
304
305 fn fill_tabstops(&mut self, start: usize, end: usize) {
307 assert!(end <= self.tabstops.len());
308
309 for i in start..end {
310 if i > 0 && i % 8 == 0 {
311 self.tabstops.set(i, true);
312 }
313 }
314 }
315
316 fn dump_tabstops(&self, buf: &mut Vec<u8>) {
321 let controls = term::control_codes();
322 if self.tabstops.len() > 8 && self.tabstops.not_any() {
323 ControlCodes::tab_clear(Some(3)).term_input_into(buf);
326 return;
327 }
328
329 let mut codes = vec![];
330 for i in 0..self.tabstops.len() {
331 let bit = self.tabstops.get(i).is_some_and(|b| *b);
332 let i: u16 = match i.try_into() {
333 Ok(i) => i,
334 Err(e) => {
335 warn!(self.logger, "generating tabstop codes: index out of bounds: {:?}", e);
336 return;
337 }
338 };
339 if i > 0 && i % 8 == 0 {
340 if !bit {
342 codes.push(ControlCodes::cursor_position(1, i + 1));
343 codes.push(ControlCodes::tab_clear(None));
344 }
345 } else {
346 if bit {
348 codes.push(ControlCodes::cursor_position(1, i + 1));
349 codes.push(controls.horizontal_tab_set.clone());
350 }
351 }
352 }
353
354 if !codes.is_empty() {
355 for code in codes.into_iter() {
356 code.term_input_into(buf);
357 }
358 ControlCodes::cursor_position(1, 1).term_input_into(buf);
359 }
360 }
361
362 fn dump_contents_into(&self, buf: &mut Vec<u8>, dump_region: ContentRegion) {
363 self.dump_tabstops(buf);
364
365 match self.screen_mode {
366 ScreenMode::Scrollback => self.scrollback.dump_contents_into(buf, dump_region),
367 ScreenMode::Alt => self.altscreen.dump_contents_into(buf, dump_region),
368 }
369
370 let controls = term::control_codes();
371
372 controls.clear_attrs.term_input_into(buf);
375 let mut cursor_attrs = self.cursor_attrs.clone();
376 cursor_attrs.link_target = None;
382 let codes = term::Attrs::default().transition_to(&cursor_attrs);
383 for c in codes.into_iter() {
384 c.term_input_into(buf);
385 }
386 if self.cursor_style != term::CursorStyle::Default {
387 self.cursor_style.term_input_into(buf);
388 }
389
390 match (self.title_stack.last(), self.icon_name_stack.last()) {
395 (Some(title), Some(icon_name)) if !title.is_empty() && title == icon_name => {
396 ControlCodes::set_title_and_icon_name(title.clone()).term_input_into(buf)
397 }
398 (Some(title), Some(icon_name)) => {
399 if !title.is_empty() {
400 ControlCodes::set_title(title.clone()).term_input_into(buf);
401 }
402 if !icon_name.is_empty() {
403 ControlCodes::set_icon_name(icon_name.clone()).term_input_into(buf);
404 }
405 }
406 (Some(title), None) => {
407 if !title.is_empty() {
408 ControlCodes::set_title(title.clone()).term_input_into(buf);
409 }
410 }
411 (None, Some(icon_name)) => {
412 if !icon_name.is_empty() {
413 ControlCodes::set_icon_name(icon_name.clone()).term_input_into(buf);
414 }
415 }
416 (None, None) => {}
417 }
418
419 if let Some(working_dir) = &self.working_dir {
420 ControlCodes::set_working_dir(working_dir.host.clone(), working_dir.dir.clone())
421 .term_input_into(buf);
422 }
423
424 if !self.palette_overrides.is_empty() {
425 ControlCodes::set_color_indices(
426 self.palette_overrides
427 .iter()
428 .map(|(idx, color_spec)| (*idx, SmallVec::from(color_spec.as_slice()))),
429 )
430 .term_input_into(buf);
431 }
432
433 if self.cursor_hidden {
434 controls.hide_cursor.term_input_into(buf);
435 }
436 if let Some(blinking) = self.cursor_blinking {
437 if blinking {
438 controls.enable_cursor_blink.term_input_into(buf);
439 } else {
440 controls.disable_cursor_blink.term_input_into(buf);
441 }
442 }
443 if self.application_keypad_mode_enabled {
444 controls.enable_application_keypad_mode.term_input_into(buf);
445 }
446 if self.report_focus {
447 controls.enable_report_focus.term_input_into(buf);
448 }
449 if self.in_paste_mode {
450 controls.enable_paste_mode.term_input_into(buf);
451 }
452 if self.insert_mode {
453 controls.enable_insert_mode.term_input_into(buf);
454 }
455
456 let mut functional_color_idx = 0;
459 while functional_color_idx < self.functional_colors.len() {
460 if let Some(color_spec) = &self.functional_colors[functional_color_idx] {
461 let start_idx = functional_color_idx;
462 let mut color_specs = vec![color_spec.as_slice()];
463
464 functional_color_idx += 1;
465 while functional_color_idx < self.functional_colors.len() {
466 if let Some(s) = &self.functional_colors[functional_color_idx] {
467 color_specs.push(s.as_slice());
468 } else {
469 break;
470 }
471 functional_color_idx += 1;
472 }
473
474 ControlCodes::set_functional_color(start_idx, color_specs).term_input_into(buf);
475 }
476
477 functional_color_idx += 1;
478 }
479 }
480
481 fn set_functional_color<'a, I>(&mut self, mut idx: usize, mut params_iter: I)
484 where
485 I: Iterator<Item = &'a &'a [u8]>,
486 {
487 while let Some(color_spec) = params_iter.next() {
488 if idx >= self.functional_colors.len() {
489 return;
490 }
491
492 if *color_spec != [b'?'] {
493 self.functional_colors[idx] = Some(Vec::from(*color_spec));
494 }
495
496 idx += 1;
497 }
498 }
499
500 fn set_title(&mut self, title: SmallVec<[u8; 8]>) {
501 if let Some(top) = self.title_stack.last_mut() {
502 *top = title;
503 } else {
504 self.title_stack.push(title);
505 }
506 }
507
508 fn set_icon_name(&mut self, icon_name: SmallVec<[u8; 8]>) {
509 if let Some(top) = self.icon_name_stack.last_mut() {
510 *top = icon_name;
511 } else {
512 self.icon_name_stack.push(icon_name);
513 }
514 }
515
516 fn write_char_at_cursor(&mut self, cell: Cell) {
517 let insert_mode = self.insert_mode;
518 let screen = self.screen_mut();
519 screen.snap_to_bottom();
520
521 if insert_mode {
532 let width = screen.size.width;
533 let col = screen.cursor.col;
534 if col < width {
535 if let Some(l) = screen.get_line_mut() {
536 l.insert_character(width, col, cell.width() as usize);
537 }
538 }
539 }
540
541 if let Err(e) = screen.write_at_cursor(cell) {
542 warn!(self.logger, "writing char at cursor: {:?}", e);
543 }
544 }
545}
546
547enum ScreenMode {
549 Scrollback,
550 Alt,
551}
552
553impl vte::Perform for State {
554 fn print(&mut self, c: char) {
555 trace!(self.logger, "print: {}", c);
556 self.last_print_char = Some(c);
557 let attrs = self.cursor_attrs.clone();
558 self.write_char_at_cursor(Cell::new(c, attrs));
559 }
560
561 fn execute(&mut self, byte: u8) {
562 self.last_print_char = None;
563 trace!(self.logger, "execute: byte {}", byte);
564 match byte {
565 b'\n' => {
566 let screen = self.screen_mut();
567 let (scroll_top, scroll_bottom) =
568 screen.scroll_region(false).as_region(&screen.size).row_bounds();
569 let within_scroll =
570 scroll_top <= screen.cursor.row && screen.cursor.row < scroll_bottom;
571 screen.cursor.row += 1;
572 if within_scroll {
573 if screen.cursor.row >= scroll_bottom {
574 screen.scroll_down(1);
575 screen.cursor.row -= 1;
576 }
577 } else {
578 screen.clamp();
579 }
580 }
581 b'\r' => self.screen_mut().cursor.col = 0,
582 b'\t' => {
583 let mut col = self.screen().cursor.col;
584 col += 1;
585 while col < self.tabstops.len() && !self.tabstops.get(col).is_some_and(|b| *b) {
586 col += 1;
587 }
588
589 let screen = self.screen_mut();
590 screen.cursor.col = col;
591 screen.clamp();
592 }
593 b'\x08' => {
594 let screen = self.screen_mut();
596 screen.cursor.col = screen.cursor.col.saturating_sub(1);
597 }
598 b'\x07' => {}
600 _ => {
601 warn!(self.logger, "execute: unhandled byte {}", byte);
602 }
603 }
604 }
605
606 fn hook(&mut self, _params: &vte::Params, intermediates: &[u8], ignore: bool, action: char) {
607 self.last_print_char = None;
608 debug!(
609 self.logger,
610 "unhandled hook{}: {:?} {}",
611 if ignore { " (ignored)" } else { "" },
612 intermediates,
613 action
614 );
615 }
616
617 fn put(&mut self, byte: u8) {
618 trace!(self.logger, "unhandled put: {}", byte);
619 self.last_print_char = None;
620 }
621
622 fn unhook(&mut self) {
623 debug!(self.logger, "unhandled unhook");
624 self.last_print_char = None;
625 }
626
627 #[rustfmt::skip]
636 fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
637 trace!(self.logger, "osc_dispatch: {:?}", params);
638 self.last_print_char = None;
639
640 let mut params_iter = params.iter();
641 match params_iter.next() {
642 Some([b'0']) => if let Some(title) = params_iter.next() {
644 let title: SmallVec<[u8; 8]> = title.to_vec().into();
645 self.set_title(title.clone());
646 self.set_icon_name(title);
647 } else {
648 warn!(self.logger, "OSC 0 with no title param");
649 },
650 Some([b'1']) => if let Some(icon_name) = params_iter.next() {
651 let icon_name: SmallVec<[u8; 8]> = icon_name.to_vec().into();
652 self.set_icon_name(icon_name);
653 } else {
654 warn!(self.logger, "OSC 1 with no icon_name param");
655 },
656 Some([b'2']) => if let Some(title) = params_iter.next() {
657 let title: SmallVec<[u8; 8]> = title.to_vec().into();
658 self.set_title(title);
659 } else {
660 warn!(self.logger, "OSC 2 with no title param");
661 },
662
663 Some([b'4']) => while let (Some(idx), Some(color_spec)) = (params_iter.next(), params_iter.next()) {
665 if *color_spec == [b'?'] {
666 continue;
670 }
671
672 match std::str::from_utf8(idx) {
673 Ok(s) => match s.parse::<usize>() {
674 Ok(i) => {
675 self.palette_overrides.insert(i, color_spec.to_vec());
676 },
677 Err(e) => warn!(self.logger, "OSC 4: idx is an invalid number '{}': {}", s, e),
678 },
679 Err(e) => warn!(self.logger, "OSC 4: invalid idx '{:?}': {}", idx, e),
680 }
681 },
682 Some([b'1', b'0', b'4']) => while let Some(idx) = params_iter.next() {
683 match std::str::from_utf8(idx) {
684 Ok(s) => match s.parse::<usize>() {
685 Ok(i) => {
686 self.palette_overrides.remove(&i);
687 },
688 Err(e) => warn!(self.logger, "OSC 104: idx is an invalid number '{}': {}", s, e),
689 },
690 Err(e) => warn!(self.logger, "OSC 104: invalid idx '{:?}': {}", idx, e),
691 }
692 },
693
694 Some([b'7']) => if let (Some(host), Some(dir)) = (params_iter.next(), params_iter.next()) {
696 self.working_dir = Some(WorkingDir {
697 host: host.to_vec().into(),
698 dir: dir.to_vec().into(),
699 });
700 } else {
701 warn!(self.logger, "OSC 7 with fewer than 2 params");
702 },
703
704 Some([b'8']) => if let (Some(params), Some(url)) = (params_iter.next(), params_iter.next()) {
706 if params.is_empty() && url.is_empty() {
707 self.cursor_attrs.link_target = None;
708 } else {
709 self.cursor_attrs.link_target = Some(LinkTarget {
710 params: SmallVec::from_slice(params),
711 url: SmallVec::from_slice(url),
712 });
713 }
714 } else {
715 self.cursor_attrs.link_target = None;
716 },
717
718 Some([b'1', x]) if b'0' <= *x && *x <= b'9' =>
720 self.set_functional_color((*x - b'0') as usize, params_iter),
721
722 Some([b'5', b'2']) => debug!(self.logger, "ignoring OSC 52 (clipboard)"),
723 Some([b'9']) => debug!(self.logger, "ignoring OSC 9 (desktop notification)"),
724 Some([b'7', b'7', b'7']) => debug!(self.logger, "ignoring OSC 777"),
725 Some([b'1', b'3', b'3']) => debug!(self.logger, "ignoring OSC 133 (iterm2 marks)"),
726 Some([b'3', b'0', b'0', b'8']) => debug!(self.logger, "ignoring OSC 3008 (systemd context signaling)"),
727
728 _ => warn!(self.logger, "unhandled 'OSC {:?} {}'", params, if bell_terminated {
729 "BEL"
730 } else {
731 "ST"
732 }),
733 }
734 }
735
736 #[rustfmt::skip]
743 fn csi_dispatch(
744 &mut self,
745 params: &vte::Params,
746 intermediates: &[u8],
747 ignore: bool,
748 action: char,
749 ) {
750 if ignore {
751 warn!(self.logger, "malformed CSI seq");
752 return;
753 }
754 if tracing::enabled!(tracing::Level::TRACE) {
755 trace!(self.logger, "csi_dispatch: intermediates={:?} params={:?} {}",
756 intermediates, params.iter().collect::<Vec<_>>(), action);
757 }
758
759 let mut params_iter = params.iter();
760
761 if action != 'b' || !intermediates.is_empty() {
762 self.last_print_char = None;
763 }
764
765 match action {
766 'A' => {
768 let n = param_or(&mut params_iter, 1) as usize;
769 let screen = self.screen_mut();
770 screen.cursor.row = screen.cursor.row.saturating_sub(n);
771 screen.clamp();
772 }
773 'B' => {
775 let n = param_or(&mut params_iter, 1) as usize;
776 let screen = self.screen_mut();
777 screen.cursor.row += n;
778 screen.clamp();
779 }
780 'C' => {
782 let n = param_or(&mut params_iter, 1) as usize;
783 let screen = self.screen_mut();
784 screen.cursor.col += n;
785 screen.clamp();
786 }
787 'D' => {
789 let n = param_or(&mut params_iter, 1) as usize;
790 let screen = self.screen_mut();
791 screen.cursor.col = screen.cursor.col.saturating_sub(n);
792 screen.clamp();
793 }
794 'E' => {
796 let n = param_or(&mut params_iter, 1) as usize;
797 let screen = self.screen_mut();
798 screen.cursor.row += n;
799 screen.cursor.col = 0;
800 screen.clamp();
801 }
802 'F' => {
804 let n = param_or(&mut params_iter, 1) as usize;
805 let screen = self.screen_mut();
806 screen.cursor.row = screen.cursor.row.saturating_sub(n);
807 screen.cursor.col = 0;
808 screen.clamp();
809 }
810 '`' | 'G' => {
813 let n = param_or(&mut params_iter, 1) as usize;
814 let n = n.saturating_sub(1); let screen = self.screen_mut();
817 screen.cursor.col = n;
818 screen.clamp();
819 }
820 'f' | 'H' => {
823 let row = param_or(&mut params_iter, 1) as usize;
825 let col = param_or(&mut params_iter, 1) as usize;
826 let screen = self.screen_mut();
827 screen.set_cursor(term::Pos { row, col });
828 screen.clamp();
829 }
830 'J' => while let Some(code) = params_iter.next() {
832 match code {
833 [] | [0] => self.screen_mut().erase_to_end(),
834 [1] => self.screen_mut().erase_from_start(),
835 [2] => self.screen_mut().erase(false),
836 [3] => self.screen_mut().erase(true),
837 _ => warn!(self.logger, "unhandled 'CSI {:?} J'", code),
838 }
839 }
840 'K' => while let Some(code) = params_iter.next() {
842 match code {
843 [] | [0] => {
844 let screen = self.screen_mut();
845 let col = screen.cursor.col;
846 if let Some(l) = screen.get_line_mut() {
847 l.erase(line::Section::ToEnd(col));
848 }
849 }
850 [1] => {
851 let screen = self.screen_mut();
852 let col = screen.cursor.col;
853 if let Some(l) = screen.get_line_mut() {
854 l.erase(line::Section::StartTo(col));
855 }
856 }
857 [2] => if let Some(l) = self.screen_mut().get_line_mut() {
858 l.erase(line::Section::Whole);
859 }
860 _ => warn!(self.logger, "unhandled 'CSI {:?} K'", code),
861 }
862 }
863 'L' => {
865 let n = param_or(&mut params_iter, 1) as usize;
866 self.screen_mut().insert_lines(n);
867 }
868 'M' => {
870 let n = param_or(&mut params_iter, 1) as usize;
871 self.screen_mut().delete_lines(n);
872 }
873 'S' => {
875 let n = param_or(&mut params_iter, 1) as usize;
876 self.screen_mut().scroll_up(n as usize);
877 }
878 'W' => {
880 let code = param_or(&mut params_iter, 0) as usize;
881 match code {
882 0 => {
883 let col = self.screen().cursor.col;
884 self.tabstops.set(col, true);
885 },
886 2 => {
887 let col = self.screen().cursor.col;
888 self.tabstops.set(col, false);
889 }
890 5 => {
891 self.tabstops.fill(false);
892 }
893 _ => warn!(self.logger, "unhandled 'CSI {:?} W'", code),
894 }
895 }
896 'Z' if intermediates.is_empty() => {
898 let n = param_or(&mut params_iter, 1) as usize;
899 let mut col = self.screen().cursor.col;
900 for _ in 0..n {
901 if col == 0 {
902 break;
903 }
904 col -= 1;
905 while col > 0 && !self.tabstops.get(col).is_some_and(|b| *b) {
906 col -= 1;
907 }
908 }
909
910 let screen = self.screen_mut();
911 screen.cursor.col = col;
912 screen.clamp();
913 }
914 'T' => {
916 let n = param_or(&mut params_iter, 1) as usize;
917 self.screen_mut().scroll_down(n as usize);
918 }
919
920 '@' => {
922 let n = param_or(&mut params_iter, 1) as usize;
923
924 let screen = self.screen_mut();
925 let width = screen.size.width;
926 let col = screen.cursor.col;
927 if let Some(l) = screen.get_line_mut() {
928 l.insert_character(width, col, n);
929 }
930 }
931 'P' => {
933 let n = param_or(&mut params_iter, 1) as usize;
934
935 let attrs = self.cursor_attrs.clone();
936
937 let screen = self.screen_mut();
938 let width = screen.size.width;
939 let col = screen.cursor.col;
940 if let Some(l) = screen.get_line_mut() {
941 l.delete_character(width, col, &attrs, n);
942 }
943 }
944 'X' => {
946 let n = param_or(&mut params_iter, 1) as usize;
947
948 let attrs = self.cursor_attrs.clone();
949
950 let screen = self.screen_mut();
951 let width = screen.size.width;
952 let col = screen.cursor.col;
953 if let Some(l) = screen.get_line_mut() {
954 l.erase_character(width, col, &attrs, n);
955 }
956 }
957 'b' if intermediates.is_empty() => if let Some(c) = self.last_print_char {
959 let n = param_or(&mut params_iter, 1) as usize;
960
961 let cell = Cell::new(c, self.cursor_attrs.clone());
962 for _ in 0..n {
963 self.write_char_at_cursor(cell.clone());
964 }
965 }
966 'c' => debug!(self.logger, "CSI ... c - device attribute query"),
967 'd' => {
969 let row = param_or(&mut params_iter, 1) as usize;
970 let col = self.screen().cursor.col + 1;
971 let screen = self.screen_mut();
972 screen.set_cursor(term::Pos { row, col });
973 screen.clamp();
974 }
975
976 's' => {
978 let screen = self.screen_mut();
979 let cursor = screen.cursor.clone();
980 screen.saved_cursor.pos = cursor;
981 }
982 't' => while let Some(code) = params_iter.next() {
984 match code {
985 [] | [0] => debug!(self.logger, "CSI 0 t - ignoring"),
986 [14, ..] => debug!(self.logger, "CSI 14 t - pixel size query"),
987 [16, ..] => debug!(self.logger, "CSI 16 t - cell size query"),
988 [18, ..] => debug!(self.logger, "CSI 18 t - term size query"),
989 [19, ..] => debug!(self.logger, "CSI 19 t - display size query"),
990 [22] => {
991 let code = param_or(&mut params_iter, 0) as usize;
992 if (code == 0 || code == 1) && self.icon_name_stack.len() < MAX_TITLE_STACK_DEPTH {
993 if let Some(icon_name) = self.icon_name_stack.last().cloned() {
994 self.icon_name_stack.push(icon_name);
995 } else {
996 self.icon_name_stack.push(SmallVec::new());
997 }
998 }
999
1000 if (code == 0 || code == 2) && self.title_stack.len() < MAX_TITLE_STACK_DEPTH {
1001 if let Some(title) = self.title_stack.last().cloned() {
1002 self.title_stack.push(title);
1003 } else {
1004 self.title_stack.push(SmallVec::new());
1005 }
1006 }
1007 }
1008 [23] => {
1009 let code = param_or(&mut params_iter, 0) as usize;
1010 if code == 0 || code == 1 {
1011 self.icon_name_stack.pop();
1012 }
1013
1014 if code == 0 || code == 2 {
1015 self.title_stack.pop();
1016 }
1017 }
1018 _ => warn!(self.logger, "unhandled CSI ... {:?} t", code),
1019 }
1020 }
1021 'u' => {
1023 let screen = self.screen_mut();
1024 screen.cursor = screen.saved_cursor.pos;
1025 screen.clamp();
1026 }
1027
1028 'g' => {
1030 let code = param_or(&mut params_iter, 0) as usize;
1031 match code {
1032 0 => {
1033 let col = self.screen().cursor.col;
1034 self.tabstops.set(col, false);
1035 },
1036 3 => {
1037 self.tabstops.fill(false);
1038 }
1039 _ => warn!(self.logger, "unhandled 'CSI {:?} g'", code),
1040 }
1041 }
1042
1043 'h' => match intermediates {
1044 [] => while let Some(code) = params_iter.next() {
1045 match code {
1046 [4] => self.insert_mode = true,
1047 _ => {
1048 warn!(
1049 self.logger,
1050 "Unhandled CSI h command: CSI {:?} {:?} h",
1051 intermediates,
1052 params.iter().collect::<Vec<&[u16]>>()
1053 );
1054 return;
1055 }
1056 }
1057 }
1058 [b'?'] => while let Some(code) = params_iter.next() {
1059 match code {
1060 [1] => self.application_keypad_mode_enabled = true,
1061 [3] => {},
1064 [4] => {},
1067 [6] => self.screen_mut().set_origin_mode(OriginMode::ScrollRegion),
1068 [12] => self.cursor_blinking = Some(true),
1069 [25] => self.cursor_hidden = false,
1070 [1004] => self.report_focus = true,
1071 [1049] => {
1073 self.altscreen = Screen::alt(self.altscreen.size);
1076 self.screen_mode = ScreenMode::Alt;
1077 }
1078 [2004] => self.in_paste_mode = true,
1079 [2026] => {},
1082
1083 _ => {
1084 warn!(
1085 self.logger,
1086 "Unhandled CSI h command: CSI {:?} {:?} h",
1087 intermediates,
1088 params.iter().collect::<Vec<&[u16]>>()
1089 );
1090 return;
1091 }
1092 }
1093 }
1094 _ => warn!(
1095 self.logger,
1096 "Unhandled CSI h command: CSI {:?} {:?} h",
1097 intermediates,
1098 params.iter().collect::<Vec<&[u16]>>()
1099 ),
1100 }
1101 'l' => match intermediates {
1102 [] => while let Some(code) = params_iter.next() {
1103 match code {
1104 [4] => self.insert_mode = false,
1105 _ => {
1106 warn!(
1107 self.logger,
1108 "Unhandled CSI l command: CSI {:?} {:?} l",
1109 intermediates,
1110 params.iter().collect::<Vec<&[u16]>>()
1111 );
1112 return;
1113 }
1114 }
1115 }
1116 [b'?'] => while let Some(code) = params_iter.next() {
1117 match code {
1118 [1] => self.application_keypad_mode_enabled = false,
1119 [3] => {},
1124 [4] => {},
1127 [6] => self.screen_mut().set_origin_mode(OriginMode::Term),
1128 [12] => self.cursor_blinking = Some(false),
1129 [25] => self.cursor_hidden = true,
1130 [1004] => self.report_focus = false,
1131 [1049] => self.screen_mode = ScreenMode::Scrollback,
1132 [2004] => self.in_paste_mode = false,
1133 [2026] => {},
1136 _ => {
1137 warn!(
1138 self.logger,
1139 "Unhandled CSI l command: CSI {:?} {:?} l",
1140 intermediates,
1141 params.iter().collect::<Vec<&[u16]>>()
1142 );
1143 return;
1144 }
1145 }
1146 }
1147 _ => warn!(
1148 self.logger,
1149 "Unhandled CSI l command: CSI {:?} {:?} l",
1150 intermediates,
1151 params.iter().collect::<Vec<&[u16]>>()
1152 ),
1153 },
1154 'n' => while let Some(param) = params_iter.next() {
1156 match param {
1157 [6] => debug!(self.logger, "ignoring DSR (CSI 6 n), that's the real terminal's job"),
1166 _ => {}
1167 }
1168 },
1169
1170 'm' => while let Some(param) = params_iter.next() {
1172 match param {
1173 [] | [0] => self.cursor_attrs = term::Attrs::default(),
1174
1175 [4] => self.cursor_attrs.underline = Some(UnderlineStyle::Single),
1186 [21] => self.cursor_attrs.underline = Some(UnderlineStyle::Double),
1187 [24] => self.cursor_attrs.underline = None,
1188
1189 [1] => self.cursor_attrs.font_weight = Some(FontWeight::Bold),
1191 [2] => self.cursor_attrs.font_weight = Some(FontWeight::Faint),
1192 [22] => self.cursor_attrs.font_weight = None,
1193
1194 [3] => self.cursor_attrs.italic = true,
1196 [23] => self.cursor_attrs.italic = false,
1197
1198 [7] => self.cursor_attrs.inverse = true,
1200 [27] => self.cursor_attrs.inverse = false,
1201
1202 [5] => self.cursor_attrs.blink = Some(BlinkStyle::Slow),
1204 [6] => self.cursor_attrs.blink = Some(BlinkStyle::Rapid),
1205 [25] => self.cursor_attrs.blink = None,
1206
1207 [8] => self.cursor_attrs.conceal = true,
1209 [28] => self.cursor_attrs.conceal = false,
1210
1211 [9] => self.cursor_attrs.strikethrough = true,
1213 [29] => self.cursor_attrs.strikethrough = false,
1214
1215 [51] => self.cursor_attrs.framed = Some(FrameStyle::Frame),
1217 [52] => self.cursor_attrs.framed = Some(FrameStyle::Circle),
1218 [54] => self.cursor_attrs.framed = None,
1219
1220 [53] => self.cursor_attrs.overline = true,
1222 [55] => self.cursor_attrs.overline = false,
1223
1224 [59] => self.cursor_attrs.underline_color = term::Color::Default,
1226 param if !param.is_empty() && param[0] == 58 => {
1227 match parse_extended_color(param, &mut params_iter) {
1228 Some(color) => self.cursor_attrs.underline_color = color,
1229 None => warn!(self.logger, "unhandled incomplete 'CSI 58 ... m'"),
1230 }
1231 }
1232
1233 [49] => self.cursor_attrs.bgcolor = term::Color::Default,
1235 [n] if 40 <= *n && *n < 48 => match (*n - 40).try_into() {
1236 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
1237 Err(e) => warn!(self.logger, "out of bounds bgcolor idx (1): {:?}", e),
1238 }
1239 [n] if 100 <= *n && *n < 108 => match (*n - 92).try_into() {
1240 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
1241 Err(e) => warn!(self.logger, "out of bounds bgcolor idx (2): {:?}", e),
1242 }
1243 param if !param.is_empty() && param[0] == 48 => {
1244 match parse_extended_color(param, &mut params_iter) {
1245 Some(color) => self.cursor_attrs.bgcolor = color,
1246 None => warn!(self.logger, "unhandled incomplete 'CSI 48 ... m'"),
1247 }
1248 }
1249
1250 [39] => self.cursor_attrs.fgcolor = term::Color::Default,
1252 [n] if 30 <= *n && *n < 38 => match (*n - 30).try_into() {
1253 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
1254 Err(e) => warn!(self.logger, "out of bounds fgcolor idx (1): {:?}", e),
1255 }
1256 [n] if 90 <= *n && *n < 98 => match (*n - 82).try_into() {
1257 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
1258 Err(e) => warn!(self.logger, "out of bounds fgcolor idx (2): {:?}", e),
1259 }
1260 param if !param.is_empty() && param[0] == 38 => {
1261 match parse_extended_color(param, &mut params_iter) {
1262 Some(color) => self.cursor_attrs.fgcolor = color,
1263 None => warn!(self.logger, "unhandled incomplete 'CSI 38 ... m'"),
1264 }
1265 }
1266
1267 _ => warn!(self.logger, "unhandled 'CSI {:?} m'", param),
1268 }
1269 }
1270 'p' => match intermediates {
1271 [b'!'] => {
1273 self.tabstops.fill(false);
1274 let width = self.screen().size.width;
1275 self.fill_tabstops(0, width);
1276 self.cursor_style = term::CursorStyle::Default;
1277 self.cursor_attrs = term::Attrs::default();
1278 self.cursor_blinking = None;
1279 self.insert_mode = false;
1280
1281 warn!(self.logger, "DECSTR only partially handled");
1282 }
1283 [b'?', b'$'] => {
1285 debug!(self.logger, "ignoring DECRQM query: params={:?}", params.iter().collect::<Vec<_>>());
1297 }
1298 _ => warn!(
1299 self.logger,
1300 "Unhandled CSI p command: CSI {:?} {:?} p",
1301 intermediates,
1302 params.iter().collect::<Vec<&[u16]>>()
1303 ),
1304 },
1305 'q' if intermediates == [b' '] => {
1307 let code = param_or(&mut params_iter, 0) as usize;
1308 match term::CursorStyle::try_from(code) {
1309 Ok(style) => self.cursor_style = style,
1310 Err(e) => warn!(self.logger, "parsing cursor style: {:?}", e),
1311 }
1312 },
1313 'r' => {
1315 let top = maybe_param(&mut params_iter);
1316 let bottom = maybe_param(&mut params_iter);
1317
1318 let screen = self.screen_mut();
1319 screen.set_scroll_region(match (top, bottom) {
1320 (None, None) => term::ScrollRegion::TrackSize,
1321 (Some(t), None) => term::ScrollRegion::Window {
1322 top: t.saturating_sub(1) as usize,
1323 bottom: screen.size.height,
1324 },
1325 (None, Some(b)) => term::ScrollRegion::Window {
1326 top: 0,
1327 bottom: b as usize,
1328 },
1329 (Some(t), Some(b)) => term::ScrollRegion::Window {
1330 top: t.saturating_sub(1) as usize,
1331 bottom: b as usize,
1332 }
1333 });
1334 }
1335
1336 _ => {
1337 warn!(self.logger, "unhandled action {}", action);
1338 }
1339 }
1340 }
1341
1342 fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
1343 if ignore {
1344 warn!(self.logger, "malformed ESC seq");
1345 return;
1346 }
1347 trace!(self.logger, "esc_dispatch: {}", byte);
1348 self.last_print_char = None;
1349
1350 match (intermediates, byte) {
1351 ([], b'7') => {
1353 let attrs = self.cursor_attrs.clone();
1354 let screen = self.screen_mut();
1355 let pos = screen.cursor.clone();
1356 screen.saved_cursor = SavedCursor { pos, attrs };
1357 }
1358 ([], b'8') => {
1360 let screen = self.screen_mut();
1361 screen.cursor = screen.saved_cursor.pos;
1362 self.cursor_attrs = screen.saved_cursor.attrs.clone();
1363 }
1364 ([], b'H') => {
1366 let col = self.screen().cursor.col;
1367 self.tabstops.set(col, true);
1368 }
1369 ([], b'M') => {
1371 let screen = self.screen_mut();
1372 let (scroll_top, _) =
1373 screen.scroll_region(false).as_region(&screen.size).row_bounds();
1374
1375 if screen.cursor.row == scroll_top {
1376 screen.insert_lines(1);
1377 } else if screen.cursor.row > 0 {
1378 screen.cursor.row -= 1;
1379 }
1380 }
1381 ([], b'c') => {
1383 self.tabstops.fill(false);
1384 let width = self.screen().size.width;
1385 self.fill_tabstops(0, width);
1386 self.cursor_style = term::CursorStyle::Default;
1387 self.cursor_attrs = term::Attrs::default();
1388 self.cursor_blinking = None;
1389 self.insert_mode = false;
1390
1391 warn!(self.logger, "RIS only partially handled");
1392 }
1393
1394 ([], b'=') => self.application_keypad_mode_enabled = true,
1395 ([], b'>') => self.application_keypad_mode_enabled = false,
1396
1397 ([b'(' | b')' | b'*' | b'+'], b'B' | b'A') => {}
1400
1401 ([], 92) => {}
1404
1405 _ => warn!(self.logger, "unhandled ESC seq ({:?}, {})", intermediates, byte),
1406 }
1407 }
1408
1409 fn terminated(&self) -> bool {
1410 false
1411 }
1412}
1413
1414fn param_or<'params>(params: &mut vte::ParamsIter<'params>, default: u16) -> u16 {
1415 maybe_param(params).unwrap_or(default)
1416}
1417
1418fn maybe_param<'params>(params: &mut vte::ParamsIter<'params>) -> Option<u16> {
1419 match params.next() {
1420 Some([0]) => None,
1421 Some([p]) => Some(*p),
1422 _ => None,
1423 }
1424}
1425
1426fn parse_extended_color<'params>(
1427 first_param: &[u16],
1428 params_iter: &mut vte::ParamsIter<'params>,
1429) -> Option<term::Color> {
1430 if first_param.len() > 1 {
1431 match first_param[1] {
1434 5 => {
1435 if first_param.len() >= 3 {
1436 let idx = first_param[2].try_into().ok()?;
1437 Some(term::Color::Idx(idx))
1438 } else {
1439 None
1440 }
1441 }
1442 2 => {
1443 if first_param.len() == 5 {
1444 let r = first_param[2].try_into().ok()?;
1445 let g = first_param[3].try_into().ok()?;
1446 let b = first_param[4].try_into().ok()?;
1447 Some(term::Color::Rgb(r, g, b))
1448 } else if first_param.len() >= 6 {
1449 let r = first_param[3].try_into().ok()?;
1452 let g = first_param[4].try_into().ok()?;
1453 let b = first_param[5].try_into().ok()?;
1454 Some(term::Color::Rgb(r, g, b))
1455 } else {
1456 None
1457 }
1458 }
1459 _ => None,
1460 }
1461 } else {
1462 match params_iter.next() {
1464 Some([5]) => {
1465 let n = param_or(params_iter, 0);
1466 let idx = n.try_into().ok()?;
1467 Some(term::Color::Idx(idx))
1468 }
1469 Some([2]) => {
1470 let r = param_or(params_iter, 0);
1471 let g = param_or(params_iter, 0);
1472 let b = param_or(params_iter, 0);
1473 let r = r.try_into().ok()?;
1474 let g = g.try_into().ok()?;
1475 let b = b.try_into().ok()?;
1476 Some(term::Color::Rgb(r, g, b))
1477 }
1478 _ => None,
1479 }
1480 }
1481}
1482
1483const NONE_VEC: Option<Vec<u8>> = None;