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 term::control_codes().clear_attrs.term_input_into(&mut buf);
128 term::ControlCodes::cursor_position(1, 1).term_input_into(&mut buf);
129 term::control_codes().clear_screen.term_input_into(&mut buf);
130 self.state.dump_contents_into(&mut buf, dump_region);
131
132 buf
133 }
134}
135
136#[derive(Debug, Eq, PartialEq, Clone)]
138pub enum ContentRegion {
139 All,
141 Screen,
143 BottomLines(usize),
145}
146
147impl std::fmt::Display for Term {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 self.state.fmt(f)
150 }
151}
152
153#[derive(Debug, Clone, Copy, Eq, PartialEq)]
155pub struct Size {
156 pub width: usize,
157 pub height: usize,
158}
159
160struct State {
162 scrollback: Screen,
164 altscreen: Screen,
166 screen_mode: ScreenMode,
168 last_print_char: Option<char>,
171 cursor_attrs: term::Attrs,
176 cursor_style: term::CursorStyle,
179 title_stack: Vec<SmallVec<[u8; 8]>>,
181 icon_name_stack: Vec<SmallVec<[u8; 8]>>,
183 working_dir: Option<WorkingDir>,
186 palette_overrides: BTreeMap<usize, Vec<u8>>,
190 functional_colors: [Option<Vec<u8>>; 10],
193 cursor_hidden: bool,
196 application_keypad_mode_enabled: bool,
199 report_focus: bool,
206 in_paste_mode: bool,
208 tabstops: BitVec,
212 logger: log::Context,
213}
214
215struct WorkingDir {
216 host: SmallVec<[u8; 8]>,
217 dir: SmallVec<[u8; 8]>,
218}
219
220impl std::fmt::Display for State {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 match self.screen_mode {
223 ScreenMode::Scrollback => {
224 writeln!(f, "Screen Mode: Scrollback")?;
225 write!(f, "{}", self.scrollback)?;
226 }
227 ScreenMode::Alt => {
228 writeln!(f, "Screen Mode: AltScreen")?;
229 write!(f, "{}", self.altscreen)?;
230 }
231 }
232
233 Ok(())
234 }
235}
236
237impl State {
238 fn new(scrollback_lines: usize, size: Size) -> Self {
239 let mut st = State {
240 scrollback: Screen::scrollback(scrollback_lines, size),
241 altscreen: Screen::alt(size),
242 screen_mode: ScreenMode::Scrollback,
243 cursor_attrs: term::Attrs::default(),
244 cursor_style: term::CursorStyle::Default,
245 title_stack: vec![],
246 icon_name_stack: vec![],
247 working_dir: None,
248 palette_overrides: BTreeMap::new(),
249 functional_colors: [NONE_VEC; 10],
250 cursor_hidden: false,
251 application_keypad_mode_enabled: false,
252 report_focus: false,
253 in_paste_mode: false,
254 tabstops: bitvec![0; size.width],
255 last_print_char: None,
256 logger: log::Context::None,
257 };
258 st.fill_tabstops(0, size.width);
259 st
260 }
261
262 fn set_logger(&mut self, logger: log::Context) {
263 self.scrollback.set_logger(logger.clone());
264 self.altscreen.set_logger(logger.clone());
265 self.logger = logger;
266 }
267
268 fn screen_mut(&mut self) -> &mut Screen {
269 match self.screen_mode {
270 ScreenMode::Scrollback => &mut self.scrollback,
271 ScreenMode::Alt => &mut self.altscreen,
272 }
273 }
274
275 fn screen(&self) -> &Screen {
276 match self.screen_mode {
277 ScreenMode::Scrollback => &self.scrollback,
278 ScreenMode::Alt => &self.altscreen,
279 }
280 }
281
282 fn resize(&mut self, size: Size) {
283 let orig_len = self.tabstops.len();
284 self.tabstops.resize(size.width, false);
285 if size.width > orig_len {
286 self.fill_tabstops(orig_len, size.width);
287 }
288
289 self.scrollback.resize(size);
290 self.altscreen.resize(size);
291 }
292
293 fn fill_tabstops(&mut self, start: usize, end: usize) {
295 assert!(end <= self.tabstops.len());
296
297 for i in start..end {
298 if i > 0 && i % 8 == 0 {
299 self.tabstops.set(i, true);
300 }
301 }
302 }
303
304 fn dump_tabstops(&self, buf: &mut Vec<u8>) {
309 let controls = term::control_codes();
310 if self.tabstops.len() > 8 && self.tabstops.not_any() {
311 ControlCodes::tab_clear(Some(3)).term_input_into(buf);
314 return;
315 }
316
317 let mut codes = vec![];
318 for i in 0..self.tabstops.len() {
319 let bit = self.tabstops.get(i).is_some_and(|b| *b);
320 let i: u16 = match i.try_into() {
321 Ok(i) => i,
322 Err(e) => {
323 warn!(self.logger, "generating tabstop codes: index out of bounds: {:?}", e);
324 return;
325 }
326 };
327 if i > 0 && i % 8 == 0 {
328 if !bit {
330 codes.push(ControlCodes::cursor_position(1, i + 1));
331 codes.push(ControlCodes::tab_clear(None));
332 }
333 } else {
334 if bit {
336 codes.push(ControlCodes::cursor_position(1, i + 1));
337 codes.push(controls.horizontal_tab_set.clone());
338 }
339 }
340 }
341
342 if !codes.is_empty() {
343 for code in codes.into_iter() {
344 code.term_input_into(buf);
345 }
346 ControlCodes::cursor_position(1, 1).term_input_into(buf);
347 }
348 }
349
350 fn dump_contents_into(&self, buf: &mut Vec<u8>, dump_region: ContentRegion) {
351 self.dump_tabstops(buf);
352
353 match self.screen_mode {
354 ScreenMode::Scrollback => self.scrollback.dump_contents_into(buf, dump_region),
355 ScreenMode::Alt => self.altscreen.dump_contents_into(buf, dump_region),
356 }
357
358 let controls = term::control_codes();
359
360 controls.clear_attrs.term_input_into(buf);
363 let codes = term::Attrs::default().transition_to(&self.cursor_attrs);
364 for c in codes.into_iter() {
365 c.term_input_into(buf);
366 }
367 if self.cursor_style != term::CursorStyle::Default {
368 self.cursor_style.term_input_into(buf);
369 }
370
371 match (self.title_stack.last(), self.icon_name_stack.last()) {
376 (Some(title), Some(icon_name)) if !title.is_empty() && title == icon_name => {
377 ControlCodes::set_title_and_icon_name(title.clone()).term_input_into(buf)
378 }
379 (Some(title), Some(icon_name)) => {
380 if !title.is_empty() {
381 ControlCodes::set_title(title.clone()).term_input_into(buf);
382 }
383 if !icon_name.is_empty() {
384 ControlCodes::set_icon_name(icon_name.clone()).term_input_into(buf);
385 }
386 }
387 (Some(title), None) => {
388 if !title.is_empty() {
389 ControlCodes::set_title(title.clone()).term_input_into(buf);
390 }
391 }
392 (None, Some(icon_name)) => {
393 if !icon_name.is_empty() {
394 ControlCodes::set_icon_name(icon_name.clone()).term_input_into(buf);
395 }
396 }
397 (None, None) => {}
398 }
399
400 if let Some(working_dir) = &self.working_dir {
401 ControlCodes::set_working_dir(working_dir.host.clone(), working_dir.dir.clone())
402 .term_input_into(buf);
403 }
404
405 if !self.palette_overrides.is_empty() {
406 ControlCodes::set_color_indices(
407 self.palette_overrides
408 .iter()
409 .map(|(idx, color_spec)| (*idx, SmallVec::from(color_spec.as_slice()))),
410 )
411 .term_input_into(buf);
412 }
413
414 if self.cursor_hidden {
415 controls.hide_cursor.term_input_into(buf);
416 }
417 if self.application_keypad_mode_enabled {
418 controls.enable_application_keypad_mode.term_input_into(buf);
419 }
420 if self.report_focus {
421 controls.enable_report_focus.term_input_into(buf);
422 }
423 if self.in_paste_mode {
424 controls.enable_paste_mode.term_input_into(buf);
425 }
426
427 let mut functional_color_idx = 0;
430 while functional_color_idx < self.functional_colors.len() {
431 if let Some(color_spec) = &self.functional_colors[functional_color_idx] {
432 let start_idx = functional_color_idx;
433 let mut color_specs = vec![color_spec.as_slice()];
434
435 functional_color_idx += 1;
436 while functional_color_idx < self.functional_colors.len() {
437 if let Some(s) = &self.functional_colors[functional_color_idx] {
438 color_specs.push(s.as_slice());
439 } else {
440 break;
441 }
442 functional_color_idx += 1;
443 }
444
445 ControlCodes::set_functional_color(start_idx, color_specs).term_input_into(buf);
446 }
447
448 functional_color_idx += 1;
449 }
450 }
451
452 fn set_functional_color<'a, I>(&mut self, mut idx: usize, mut params_iter: I)
455 where
456 I: Iterator<Item = &'a &'a [u8]>,
457 {
458 while let Some(color_spec) = params_iter.next() {
459 if idx >= self.functional_colors.len() {
460 return;
461 }
462
463 if *color_spec != [b'?'] {
464 self.functional_colors[idx] = Some(Vec::from(*color_spec));
465 }
466
467 idx += 1;
468 }
469 }
470
471 fn set_title(&mut self, title: SmallVec<[u8; 8]>) {
472 if let Some(top) = self.title_stack.last_mut() {
473 *top = title;
474 } else {
475 self.title_stack.push(title);
476 }
477 }
478
479 fn set_icon_name(&mut self, icon_name: SmallVec<[u8; 8]>) {
480 if let Some(top) = self.icon_name_stack.last_mut() {
481 *top = icon_name;
482 } else {
483 self.icon_name_stack.push(icon_name);
484 }
485 }
486}
487
488enum ScreenMode {
490 Scrollback,
491 Alt,
492}
493
494impl vte::Perform for State {
495 fn print(&mut self, c: char) {
496 trace!(self.logger, "print: {}", c);
497 self.last_print_char = Some(c);
498 let attrs = self.cursor_attrs.clone();
499 let screen = self.screen_mut();
500 screen.snap_to_bottom();
501 if let Err(e) = screen.write_at_cursor(Cell::new(c, attrs)) {
502 warn!(self.logger, "writing char at cursor: {:?}", e);
503 }
504 }
505
506 fn execute(&mut self, byte: u8) {
507 self.last_print_char = None;
508 trace!(self.logger, "execute: byte {}", byte);
509 match byte {
510 b'\n' => {
511 let screen = self.screen_mut();
512 let (scroll_top, scroll_bottom) =
513 screen.scroll_region(false).as_region(&screen.size).row_bounds();
514 let within_scroll =
515 scroll_top <= screen.cursor.row && screen.cursor.row < scroll_bottom;
516 screen.cursor.row += 1;
517 if within_scroll {
518 if screen.cursor.row >= scroll_bottom {
519 screen.scroll_down(1);
520 screen.cursor.row -= 1;
521 }
522 } else {
523 screen.clamp();
524 }
525 }
526 b'\r' => self.screen_mut().cursor.col = 0,
527 b'\t' => {
528 let mut col = self.screen().cursor.col;
529 col += 1;
530 while col < self.tabstops.len() && !self.tabstops.get(col).is_some_and(|b| *b) {
531 col += 1;
532 }
533
534 let screen = self.screen_mut();
535 screen.cursor.col = col;
536 screen.clamp();
537 }
538 b'\x08' => {
539 let screen = self.screen_mut();
541 screen.cursor.col = screen.cursor.col.saturating_sub(1);
542 }
543 b'\x07' => {}
545 _ => {
546 warn!(self.logger, "execute: unhandled byte {}", byte);
547 }
548 }
549 }
550
551 fn hook(&mut self, _params: &vte::Params, intermediates: &[u8], ignore: bool, action: char) {
552 self.last_print_char = None;
553 debug!(
554 self.logger,
555 "unhandled hook{}: {:?} {}",
556 if ignore { " (ignored)" } else { "" },
557 intermediates,
558 action
559 );
560 }
561
562 fn put(&mut self, byte: u8) {
563 trace!(self.logger, "unhandled put: {}", byte);
564 self.last_print_char = None;
565 }
566
567 fn unhook(&mut self) {
568 debug!(self.logger, "unhandled unhook");
569 self.last_print_char = None;
570 }
571
572 #[rustfmt::skip]
581 fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
582 trace!(self.logger, "osc_dispatch: {:?}", params);
583 self.last_print_char = None;
584
585 let mut params_iter = params.iter();
586 match params_iter.next() {
587 Some([b'0']) => if let Some(title) = params_iter.next() {
589 let title: SmallVec<[u8; 8]> = title.to_vec().into();
590 self.set_title(title.clone());
591 self.set_icon_name(title);
592 } else {
593 warn!(self.logger, "OSC 0 with no title param");
594 },
595 Some([b'1']) => if let Some(icon_name) = params_iter.next() {
596 let icon_name: SmallVec<[u8; 8]> = icon_name.to_vec().into();
597 self.set_icon_name(icon_name);
598 } else {
599 warn!(self.logger, "OSC 1 with no icon_name param");
600 },
601 Some([b'2']) => if let Some(title) = params_iter.next() {
602 let title: SmallVec<[u8; 8]> = title.to_vec().into();
603 self.set_title(title);
604 } else {
605 warn!(self.logger, "OSC 2 with no title param");
606 },
607
608 Some([b'4']) => while let (Some(idx), Some(color_spec)) = (params_iter.next(), params_iter.next()) {
610 if *color_spec == [b'?'] {
611 continue;
615 }
616
617 match std::str::from_utf8(idx) {
618 Ok(s) => match s.parse::<usize>() {
619 Ok(i) => {
620 self.palette_overrides.insert(i, color_spec.to_vec());
621 },
622 Err(e) => warn!(self.logger, "OSC 4: idx is an invalid number '{}': {}", s, e),
623 },
624 Err(e) => warn!(self.logger, "OSC 4: invalid idx '{:?}': {}", idx, e),
625 }
626 },
627 Some([b'1', b'0', b'4']) => while let Some(idx) = params_iter.next() {
628 match std::str::from_utf8(idx) {
629 Ok(s) => match s.parse::<usize>() {
630 Ok(i) => {
631 self.palette_overrides.remove(&i);
632 },
633 Err(e) => warn!(self.logger, "OSC 104: idx is an invalid number '{}': {}", s, e),
634 },
635 Err(e) => warn!(self.logger, "OSC 104: invalid idx '{:?}': {}", idx, e),
636 }
637 },
638
639 Some([b'7']) => if let (Some(host), Some(dir)) = (params_iter.next(), params_iter.next()) {
641 self.working_dir = Some(WorkingDir {
642 host: host.to_vec().into(),
643 dir: dir.to_vec().into(),
644 });
645 } else {
646 warn!(self.logger, "OSC 7 with fewer than 2 params");
647 },
648
649 Some([b'8']) => if let (Some(params), Some(url)) = (params_iter.next(), params_iter.next()) {
651 if params.is_empty() && url.is_empty() {
652 self.cursor_attrs.link_target = None;
653 } else {
654 self.cursor_attrs.link_target = Some(LinkTarget {
655 params: SmallVec::from_slice(params),
656 url: SmallVec::from_slice(url),
657 });
658 }
659 } else {
660 self.cursor_attrs.link_target = None;
661 },
662
663 Some([b'1', x]) if b'0' <= *x && *x <= b'9' =>
665 self.set_functional_color((*x - b'0') as usize, params_iter),
666
667 Some([b'5', b'2']) => debug!(self.logger, "ignoring OSC 52 (clipboard)"),
668 Some([b'9']) => debug!(self.logger, "ignoring OSC 9 (desktop notification)"),
669 Some([b'7', b'7', b'7']) => debug!(self.logger, "ignoring OSC 777"),
670 Some([b'1', b'3', b'3']) => debug!(self.logger, "ignoring OSC 133 (iterm2 marks)"),
671 Some([b'3', b'0', b'0', b'8']) => debug!(self.logger, "ignoring OSC 3008 (systemd context signaling)"),
672
673 _ => warn!(self.logger, "unhandled 'OSC {:?} {}'", params, if bell_terminated {
674 "BEL"
675 } else {
676 "ST"
677 }),
678 }
679 }
680
681 #[rustfmt::skip]
688 fn csi_dispatch(
689 &mut self,
690 params: &vte::Params,
691 intermediates: &[u8],
692 ignore: bool,
693 action: char,
694 ) {
695 if ignore {
696 warn!(self.logger, "malformed CSI seq");
697 return;
698 }
699 if tracing::enabled!(tracing::Level::TRACE) {
700 trace!(self.logger, "csi_dispatch: intermediates={:?} params={:?} {}",
701 intermediates, params.iter().collect::<Vec<_>>(), action);
702 }
703
704 let mut params_iter = params.iter();
705
706 if action != 'b' || !intermediates.is_empty() {
707 self.last_print_char = None;
708 }
709
710 match action {
711 'A' => {
713 let n = param_or(&mut params_iter, 1) as usize;
714 let screen = self.screen_mut();
715 screen.cursor.row = screen.cursor.row.saturating_sub(n);
716 screen.clamp();
717 }
718 'B' => {
720 let n = param_or(&mut params_iter, 1) as usize;
721 let screen = self.screen_mut();
722 screen.cursor.row += n;
723 screen.clamp();
724 }
725 'C' => {
727 let n = param_or(&mut params_iter, 1) as usize;
728 let screen = self.screen_mut();
729 screen.cursor.col += n;
730 screen.clamp();
731 }
732 'D' => {
734 let n = param_or(&mut params_iter, 1) as usize;
735 let screen = self.screen_mut();
736 screen.cursor.col = screen.cursor.col.saturating_sub(n);
737 screen.clamp();
738 }
739 'E' => {
741 let n = param_or(&mut params_iter, 1) as usize;
742 let screen = self.screen_mut();
743 screen.cursor.row += n;
744 screen.cursor.col = 0;
745 screen.clamp();
746 }
747 'F' => {
749 let n = param_or(&mut params_iter, 1) as usize;
750 let screen = self.screen_mut();
751 screen.cursor.row = screen.cursor.row.saturating_sub(n);
752 screen.cursor.col = 0;
753 screen.clamp();
754 }
755 '`' | 'G' => {
758 let n = param_or(&mut params_iter, 1) as usize;
759 let n = n.saturating_sub(1); let screen = self.screen_mut();
762 screen.cursor.col = n;
763 screen.clamp();
764 }
765 'f' | 'H' => {
768 let row = param_or(&mut params_iter, 1) as usize;
770 let col = param_or(&mut params_iter, 1) as usize;
771 let screen = self.screen_mut();
772 screen.set_cursor(term::Pos { row, col });
773 screen.clamp();
774 }
775 'J' => while let Some(code) = params_iter.next() {
777 match code {
778 [] | [0] => self.screen_mut().erase_to_end(),
779 [1] => self.screen_mut().erase_from_start(),
780 [2] => self.screen_mut().erase(false),
781 [3] => self.screen_mut().erase(true),
782 _ => warn!(self.logger, "unhandled 'CSI {:?} J'", code),
783 }
784 }
785 'K' => while let Some(code) = params_iter.next() {
787 match code {
788 [] | [0] => {
789 let screen = self.screen_mut();
790 let col = screen.cursor.col;
791 if let Some(l) = screen.get_line_mut() {
792 l.erase(line::Section::ToEnd(col));
793 }
794 }
795 [1] => {
796 let screen = self.screen_mut();
797 let col = screen.cursor.col;
798 if let Some(l) = screen.get_line_mut() {
799 l.erase(line::Section::StartTo(col));
800 }
801 }
802 [2] => if let Some(l) = self.screen_mut().get_line_mut() {
803 l.erase(line::Section::Whole);
804 }
805 _ => warn!(self.logger, "unhandled 'CSI {:?} K'", code),
806 }
807 }
808 'L' => {
810 let n = param_or(&mut params_iter, 1) as usize;
811 self.screen_mut().insert_lines(n);
812 }
813 'M' => {
815 let n = param_or(&mut params_iter, 1) as usize;
816 self.screen_mut().delete_lines(n);
817 }
818 'S' => {
820 let n = param_or(&mut params_iter, 1) as usize;
821 self.screen_mut().scroll_up(n as usize);
822 }
823 'W' => {
825 let code = param_or(&mut params_iter, 0) as usize;
826 match code {
827 0 => {
828 let col = self.screen().cursor.col;
829 self.tabstops.set(col, true);
830 },
831 2 => {
832 let col = self.screen().cursor.col;
833 self.tabstops.set(col, false);
834 }
835 5 => {
836 self.tabstops.fill(false);
837 }
838 _ => warn!(self.logger, "unhandled 'CSI {:?} W'", code),
839 }
840 }
841 'T' => {
843 let n = param_or(&mut params_iter, 1) as usize;
844 self.screen_mut().scroll_down(n as usize);
845 }
846
847 '@' => {
849 let n = param_or(&mut params_iter, 1) as usize;
850
851 let screen = self.screen_mut();
852 let width = screen.size.width;
853 let col = screen.cursor.col;
854 if let Some(l) = screen.get_line_mut() {
855 l.insert_character(width, col, n);
856 }
857 }
858 'P' => {
860 let n = param_or(&mut params_iter, 1) as usize;
861
862 let attrs = self.cursor_attrs.clone();
863
864 let screen = self.screen_mut();
865 let width = screen.size.width;
866 let col = screen.cursor.col;
867 if let Some(l) = screen.get_line_mut() {
868 l.delete_character(width, col, &attrs, n);
869 }
870 }
871 'X' => {
873 let n = param_or(&mut params_iter, 1) as usize;
874
875 let attrs = self.cursor_attrs.clone();
876
877 let screen = self.screen_mut();
878 let width = screen.size.width;
879 let col = screen.cursor.col;
880 if let Some(l) = screen.get_line_mut() {
881 l.erase_character(width, col, &attrs, n);
882 }
883 }
884 'b' if intermediates.is_empty() => if let Some(c) = self.last_print_char {
886 let n = param_or(&mut params_iter, 1) as usize;
887 let logger = self.logger.clone();
888
889 let cell = Cell::new(c, self.cursor_attrs.clone());
890 let screen = self.screen_mut();
891 screen.snap_to_bottom();
892 for _ in 0..n {
893 if let Err(e) = screen.write_at_cursor(cell.clone()) {
894 warn!(logger, "writing char at cursor: {:?}", e);
895 }
896 }
897 }
898 'c' => debug!(self.logger, "CSI ... c - device attribute query"),
899 'd' => {
901 let row = param_or(&mut params_iter, 1) as usize;
902 let col = self.screen().cursor.col + 1;
903 let screen = self.screen_mut();
904 screen.set_cursor(term::Pos { row, col });
905 screen.clamp();
906 }
907
908 's' => {
910 let screen = self.screen_mut();
911 let cursor = screen.cursor.clone();
912 screen.saved_cursor.pos = cursor;
913 }
914 't' => while let Some(code) = params_iter.next() {
916 match code {
917 [14] => debug!(self.logger, "CSI 14 t - pixel size query"),
918 [16] => debug!(self.logger, "CSI 16 t - cell size query"),
919 [18] => debug!(self.logger, "CSI 18 t - term size query"),
920 [19] => debug!(self.logger, "CSI 19 t - display size query"),
921 [22] => {
922 let code = param_or(&mut params_iter, 0) as usize;
923 if (code == 0 || code == 1) && self.icon_name_stack.len() < MAX_TITLE_STACK_DEPTH {
924 if let Some(icon_name) = self.icon_name_stack.last().cloned() {
925 self.icon_name_stack.push(icon_name);
926 } else {
927 self.icon_name_stack.push(SmallVec::new());
928 }
929 }
930
931 if (code == 0 || code == 2) && self.title_stack.len() < MAX_TITLE_STACK_DEPTH {
932 if let Some(title) = self.title_stack.last().cloned() {
933 self.title_stack.push(title);
934 } else {
935 self.title_stack.push(SmallVec::new());
936 }
937 }
938 }
939 [23] => {
940 let code = param_or(&mut params_iter, 0) as usize;
941 if code == 0 || code == 1 {
942 self.icon_name_stack.pop();
943 }
944
945 if code == 0 || code == 2 {
946 self.title_stack.pop();
947 }
948 }
949 _ => warn!(self.logger, "unhandled CSI ... {:?} t", code),
950 }
951 }
952 'u' => {
954 let screen = self.screen_mut();
955 screen.cursor = screen.saved_cursor.pos;
956 screen.clamp();
957 }
958
959 'g' => {
961 let code = param_or(&mut params_iter, 0) as usize;
962 match code {
963 0 => {
964 let col = self.screen().cursor.col;
965 self.tabstops.set(col, false);
966 },
967 3 => {
968 self.tabstops.fill(false);
969 }
970 _ => warn!(self.logger, "unhandled 'CSI {:?} g'", code),
971 }
972 }
973
974 'h' => match intermediates {
975 [b'?'] => while let Some(code) = params_iter.next() {
976 match code {
977 [1] => self.application_keypad_mode_enabled = true,
978 [6] => self.screen_mut().set_origin_mode(OriginMode::ScrollRegion),
979 [25] => self.cursor_hidden = false,
980 [1004] => self.report_focus = true,
981 [1049] => {
983 self.altscreen = Screen::alt(self.altscreen.size);
986 self.screen_mode = ScreenMode::Alt;
987 }
988 [2004] => self.in_paste_mode = true,
989 [2026] => {},
992
993 _ => {
994 warn!(
995 self.logger,
996 "Unhandled CSI h command: CSI {:?} {:?} h",
997 intermediates,
998 params.iter().collect::<Vec<&[u16]>>()
999 );
1000 return;
1001 }
1002 }
1003 }
1004 _ => warn!(
1005 self.logger,
1006 "Unhandled CSI h command: CSI {:?} {:?} h",
1007 intermediates,
1008 params.iter().collect::<Vec<&[u16]>>()
1009 ),
1010 }
1011 'l' => match intermediates {
1012 [b'?'] => while let Some(code) = params_iter.next() {
1013 match code {
1014 [1] => self.application_keypad_mode_enabled = false,
1015 [6] => self.screen_mut().set_origin_mode(OriginMode::Term),
1016 [25] => self.cursor_hidden = true,
1017 [1004] => self.report_focus = false,
1018 [1049] => self.screen_mode = ScreenMode::Scrollback,
1019 [2004] => self.in_paste_mode = false,
1020 [2026] => {},
1023 _ => {
1024 warn!(
1025 self.logger,
1026 "Unhandled CSI l command: CSI {:?} {:?} l",
1027 intermediates,
1028 params.iter().collect::<Vec<&[u16]>>()
1029 );
1030 return;
1031 }
1032 }
1033 }
1034 _ => warn!(
1035 self.logger,
1036 "Unhandled CSI l command: CSI {:?} {:?} l",
1037 intermediates,
1038 params.iter().collect::<Vec<&[u16]>>()
1039 ),
1040 },
1041 'n' => while let Some(param) = params_iter.next() {
1043 match param {
1044 [6] => debug!(self.logger, "ignoring DSR (CSI 6 n), that's the real terminal's job"),
1053 _ => {}
1054 }
1055 },
1056
1057 'm' => while let Some(param) = params_iter.next() {
1059 match param {
1060 [] | [0] => self.cursor_attrs = term::Attrs::default(),
1061
1062 [4] => self.cursor_attrs.underline = Some(UnderlineStyle::Single),
1073 [21] => self.cursor_attrs.underline = Some(UnderlineStyle::Double),
1074 [24] => self.cursor_attrs.underline = None,
1075
1076 [1] => self.cursor_attrs.font_weight = Some(FontWeight::Bold),
1078 [2] => self.cursor_attrs.font_weight = Some(FontWeight::Faint),
1079 [22] => self.cursor_attrs.font_weight = None,
1080
1081 [3] => self.cursor_attrs.italic = true,
1083 [23] => self.cursor_attrs.italic = false,
1084
1085 [7] => self.cursor_attrs.inverse = true,
1087 [27] => self.cursor_attrs.inverse = false,
1088
1089 [5] => self.cursor_attrs.blink = Some(BlinkStyle::Slow),
1091 [6] => self.cursor_attrs.blink = Some(BlinkStyle::Rapid),
1092 [25] => self.cursor_attrs.blink = None,
1093
1094 [8] => self.cursor_attrs.conceal = true,
1096 [28] => self.cursor_attrs.conceal = false,
1097
1098 [9] => self.cursor_attrs.strikethrough = true,
1100 [29] => self.cursor_attrs.strikethrough = false,
1101
1102 [51] => self.cursor_attrs.framed = Some(FrameStyle::Frame),
1104 [52] => self.cursor_attrs.framed = Some(FrameStyle::Circle),
1105 [54] => self.cursor_attrs.framed = None,
1106
1107 [53] => self.cursor_attrs.overline = true,
1109 [55] => self.cursor_attrs.overline = false,
1110
1111 [49] => self.cursor_attrs.bgcolor = term::Color::Default,
1113 [n] if 40 <= *n && *n < 48 => match (*n - 40).try_into() {
1114 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
1115 Err(e) => warn!(self.logger, "out of bounds bgcolor idx (1): {:?}", e),
1116 }
1117 [n] if 100 <= *n && *n < 108 => match (*n - 92).try_into() {
1118 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
1119 Err(e) => warn!(self.logger, "out of bounds bgcolor idx (2): {:?}", e),
1120 }
1121 [48] => match params_iter.next() {
1122 Some([5]) => {
1123 let n = param_or(&mut params_iter, 0);
1124 match n.try_into() {
1125 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
1126 Err(e) => warn!(self.logger, "out of bounds bgcolor idx (3): {:?}", e),
1127 }
1128 },
1129 Some([2]) => {
1130 let r = param_or(&mut params_iter, 0);
1136 let g = param_or(&mut params_iter, 0);
1137 let b = param_or(&mut params_iter, 0);
1138 if let (Ok(r), Ok(g), Ok(b)) = (r.try_into(), g.try_into(), b.try_into()) {
1139 self.cursor_attrs.bgcolor = term::Color::Rgb(r, g, b);
1140 } else {
1141 warn!(self.logger, "out of bounds color codes for CSI 48 2 ... m");
1142 }
1143 },
1144 _ => warn!(self.logger, "unhandled incomplete 'CSI 48 ... m'"),
1145 },
1146
1147 [39] => self.cursor_attrs.fgcolor = term::Color::Default,
1149 [n] if 30 <= *n && *n < 38 => match (*n - 30).try_into() {
1150 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
1151 Err(e) => warn!(self.logger, "out of bounds fgcolor idx (1): {:?}", e),
1152 }
1153 [n] if 90 <= *n && *n < 98 => match (*n - 82).try_into() {
1154 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
1155 Err(e) => warn!(self.logger, "out of bounds fgcolor idx (2): {:?}", e),
1156 }
1157 [38] => match params_iter.next() {
1158 Some([5]) => {
1159
1160 let n = param_or(&mut params_iter, 0);
1161 match n.try_into() {
1162 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
1163 Err(e) => warn!(self.logger, "out of bounds fgcolor idx (3): {:?}", e),
1164 }
1165 },
1166 Some([2]) => {
1167 let r = param_or(&mut params_iter, 0);
1173 let g = param_or(&mut params_iter, 0);
1174 let b = param_or(&mut params_iter, 0);
1175 if let (Ok(r), Ok(g), Ok(b)) = (r.try_into(), g.try_into(), b.try_into()) {
1176 self.cursor_attrs.fgcolor = term::Color::Rgb(r, g, b);
1177 } else {
1178 warn!(self.logger, "out of bounds color codes for CSI 38 2 ... m");
1179 }
1180 },
1181 _ => warn!(self.logger, "unhandled incomplete 'CSI 38 ... m'"),
1182 },
1183
1184 _ => warn!(self.logger, "unhandled 'CSI {:?} m'", param),
1185 }
1186 }
1187 'p' => match intermediates {
1188 [b'!'] => {
1190 self.tabstops.fill(false);
1191 let width = self.screen().size.width;
1192 self.fill_tabstops(0, width);
1193 self.cursor_style = term::CursorStyle::Default;
1194
1195 warn!(self.logger, "DECSTR only partially handled");
1196 }
1197 [b'?', b'$'] => {
1199 debug!(self.logger, "ignoring DECRQM query: params={:?}", params.iter().collect::<Vec<_>>());
1211 }
1212 _ => warn!(
1213 self.logger,
1214 "Unhandled CSI p command: CSI {:?} {:?} p",
1215 intermediates,
1216 params.iter().collect::<Vec<&[u16]>>()
1217 ),
1218 },
1219 'q' if intermediates == [b' '] => {
1221 let code = param_or(&mut params_iter, 0) as usize;
1222 match term::CursorStyle::try_from(code) {
1223 Ok(style) => self.cursor_style = style,
1224 Err(e) => warn!(self.logger, "parsing cursor style: {:?}", e),
1225 }
1226 },
1227 'r' => {
1229 let top = maybe_param(&mut params_iter);
1230 let bottom = maybe_param(&mut params_iter);
1231
1232 let screen = self.screen_mut();
1233 screen.set_scroll_region(match (top, bottom) {
1234 (None, None) => term::ScrollRegion::TrackSize,
1235 (Some(t), None) => term::ScrollRegion::Window {
1236 top: t.saturating_sub(1) as usize,
1237 bottom: screen.size.height,
1238 },
1239 (None, Some(b)) => term::ScrollRegion::Window {
1240 top: 0,
1241 bottom: b as usize,
1242 },
1243 (Some(t), Some(b)) => term::ScrollRegion::Window {
1244 top: t.saturating_sub(1) as usize,
1245 bottom: b as usize,
1246 }
1247 });
1248 }
1249
1250 _ => {
1251 warn!(self.logger, "unhandled action {}", action);
1252 }
1253 }
1254 }
1255
1256 fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
1257 if ignore {
1258 warn!(self.logger, "malformed ESC seq");
1259 return;
1260 }
1261 trace!(self.logger, "esc_dispatch: {}", byte);
1262 self.last_print_char = None;
1263
1264 match (intermediates, byte) {
1265 ([], b'7') => {
1267 let attrs = self.cursor_attrs.clone();
1268 let screen = self.screen_mut();
1269 let pos = screen.cursor.clone();
1270 screen.saved_cursor = SavedCursor { pos, attrs };
1271 }
1272 ([], b'8') => {
1274 let screen = self.screen_mut();
1275 screen.cursor = screen.saved_cursor.pos;
1276 self.cursor_attrs = screen.saved_cursor.attrs.clone();
1277 }
1278 ([], b'H') => {
1280 let col = self.screen().cursor.col;
1281 self.tabstops.set(col, true);
1282 }
1283 ([], b'c') => {
1285 self.tabstops.fill(false);
1286 let width = self.screen().size.width;
1287 self.fill_tabstops(0, width);
1288 self.cursor_style = term::CursorStyle::Default;
1289
1290 warn!(self.logger, "RIS only partially handled");
1291 }
1292
1293 ([], b'=') => self.application_keypad_mode_enabled = true,
1294 ([], b'>') => self.application_keypad_mode_enabled = false,
1295
1296 ([b'(' | b')' | b'*' | b'+'], b'B' | b'A') => {}
1299
1300 ([], 92) => {}
1303
1304 _ => warn!(self.logger, "unhandled ESC seq ({:?}, {})", intermediates, byte),
1305 }
1306 }
1307
1308 fn terminated(&self) -> bool {
1309 false
1310 }
1311}
1312
1313fn param_or<'params>(params: &mut vte::ParamsIter<'params>, default: u16) -> u16 {
1314 maybe_param(params).unwrap_or(default)
1315}
1316
1317fn maybe_param<'params>(params: &mut vte::ParamsIter<'params>) -> Option<u16> {
1318 match params.next() {
1319 Some([0]) => None,
1320 Some([p]) => Some(*p),
1321 _ => None,
1322 }
1323}
1324
1325const NONE_VEC: Option<Vec<u8>> = None;