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 tracing::{debug, trace, warn};
29
30#[macro_use]
31mod visibility;
32
33mod altscreen;
34mod cell;
35mod line;
36mod screen;
37mod scrollback;
38
39#[cfg(not(feature = "unstable-internal-test"))]
40mod term;
41
42#[cfg(feature = "unstable-internal-test")]
43pub mod term;
44
45const MAX_TITLE_STACK_DEPTH: usize = 64;
46
47pub struct Term {
49 parser: vte::Parser,
50 state: State,
51}
52
53impl Term {
54 pub fn new(scrollback_lines: usize, size: Size) -> Self {
63 Term { parser: vte::Parser::new(), state: State::new(scrollback_lines, size) }
64 }
65
66 pub fn size(&self) -> Size {
68 self.state.screen().size
69 }
70
71 pub fn resize(&mut self, size: Size) {
76 if size.height > self.scrollback_lines() {
77 self.set_scrollback_lines(size.height);
78 }
79
80 self.state.resize(size);
81 }
82
83 pub fn scrollback_lines(&self) -> usize {
85 self.state.scrollback.scrollback_lines().expect("scrollback screen to have lines")
86 }
87
88 pub fn set_scrollback_lines(&mut self, scrollback_lines: usize) {
96 self.state.scrollback.set_scrollback_lines(scrollback_lines);
97 }
98
99 pub fn process(&mut self, buf: &[u8]) {
102 self.parser.advance(&mut self.state, buf);
103 }
104
105 pub fn contents(&self, dump_region: ContentRegion) -> Vec<u8> {
110 let mut buf = vec![];
111 term::control_codes().clear_attrs.term_input_into(&mut buf);
112 term::ControlCodes::cursor_position(1, 1).term_input_into(&mut buf);
113 term::control_codes().clear_screen.term_input_into(&mut buf);
114 self.state.dump_contents_into(&mut buf, dump_region);
115
116 buf
117 }
118}
119
120#[derive(Debug, Eq, PartialEq, Clone)]
122pub enum ContentRegion {
123 All,
125 Screen,
127 BottomLines(usize),
129}
130
131impl std::fmt::Display for Term {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 self.state.fmt(f)
134 }
135}
136
137#[derive(Debug, Clone, Copy, Eq, PartialEq)]
139pub struct Size {
140 pub width: usize,
141 pub height: usize,
142}
143
144struct State {
146 scrollback: Screen,
148 altscreen: Screen,
150 screen_mode: ScreenMode,
152 last_print_char: Option<char>,
155 cursor_attrs: term::Attrs,
160 cursor_style: term::CursorStyle,
163 title_stack: Vec<SmallVec<[u8; 8]>>,
165 icon_name_stack: Vec<SmallVec<[u8; 8]>>,
167 working_dir: Option<WorkingDir>,
170 palette_overrides: BTreeMap<usize, Vec<u8>>,
174 functional_colors: [Option<Vec<u8>>; 10],
177 cursor_hidden: bool,
180 application_keypad_mode_enabled: bool,
183 report_focus: bool,
190 in_paste_mode: bool,
192 tabstops: BitVec,
196}
197
198struct WorkingDir {
199 host: SmallVec<[u8; 8]>,
200 dir: SmallVec<[u8; 8]>,
201}
202
203impl std::fmt::Display for State {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 match self.screen_mode {
206 ScreenMode::Scrollback => {
207 writeln!(f, "Screen Mode: Scrollback")?;
208 write!(f, "{}", self.scrollback)?;
209 }
210 ScreenMode::Alt => {
211 writeln!(f, "Screen Mode: AltScreen")?;
212 write!(f, "{}", self.altscreen)?;
213 }
214 }
215
216 Ok(())
217 }
218}
219
220impl State {
221 fn new(scrollback_lines: usize, size: Size) -> Self {
222 let mut st = State {
223 scrollback: Screen::scrollback(scrollback_lines, size),
224 altscreen: Screen::alt(size),
225 screen_mode: ScreenMode::Scrollback,
226 cursor_attrs: term::Attrs::default(),
227 cursor_style: term::CursorStyle::Default,
228 title_stack: vec![],
229 icon_name_stack: vec![],
230 working_dir: None,
231 palette_overrides: BTreeMap::new(),
232 functional_colors: [NONE_VEC; 10],
233 cursor_hidden: false,
234 application_keypad_mode_enabled: false,
235 report_focus: false,
236 in_paste_mode: false,
237 tabstops: bitvec![0; size.width],
238 last_print_char: None,
239 };
240 st.fill_tabstops(0, size.width);
241 st
242 }
243
244 fn screen_mut(&mut self) -> &mut Screen {
245 match self.screen_mode {
246 ScreenMode::Scrollback => &mut self.scrollback,
247 ScreenMode::Alt => &mut self.altscreen,
248 }
249 }
250
251 fn screen(&self) -> &Screen {
252 match self.screen_mode {
253 ScreenMode::Scrollback => &self.scrollback,
254 ScreenMode::Alt => &self.altscreen,
255 }
256 }
257
258 fn resize(&mut self, size: Size) {
259 let orig_len = self.tabstops.len();
260 self.tabstops.resize(size.width, false);
261 if size.width > orig_len {
262 self.fill_tabstops(orig_len, size.width);
263 }
264
265 self.scrollback.resize(size);
266 self.altscreen.resize(size);
267 }
268
269 fn fill_tabstops(&mut self, start: usize, end: usize) {
271 assert!(end <= self.tabstops.len());
272
273 for i in start..end {
274 if i > 0 && i % 8 == 0 {
275 self.tabstops.set(i, true);
276 }
277 }
278 }
279
280 fn dump_tabstops(&self, buf: &mut Vec<u8>) {
285 let controls = term::control_codes();
286 if self.tabstops.len() > 8 && self.tabstops.not_any() {
287 ControlCodes::tab_clear(Some(3)).term_input_into(buf);
290 return;
291 }
292
293 let mut codes = vec![];
294 for i in 0..self.tabstops.len() {
295 let bit = self.tabstops.get(i).is_some_and(|b| *b);
296 let i: u16 = match i.try_into() {
297 Ok(i) => i,
298 Err(e) => {
299 warn!("generating tabstop codes: index out of bounds: {:?}", e);
300 return;
301 }
302 };
303 if i > 0 && i % 8 == 0 {
304 if !bit {
306 codes.push(ControlCodes::cursor_position(1, i + 1));
307 codes.push(ControlCodes::tab_clear(None));
308 }
309 } else {
310 if bit {
312 codes.push(ControlCodes::cursor_position(1, i + 1));
313 codes.push(controls.horizontal_tab_set.clone());
314 }
315 }
316 }
317
318 if !codes.is_empty() {
319 for code in codes.into_iter() {
320 code.term_input_into(buf);
321 }
322 ControlCodes::cursor_position(1, 1).term_input_into(buf);
323 }
324 }
325
326 fn dump_contents_into(&self, buf: &mut Vec<u8>, dump_region: ContentRegion) {
327 self.dump_tabstops(buf);
328
329 match self.screen_mode {
330 ScreenMode::Scrollback => self.scrollback.dump_contents_into(buf, dump_region),
331 ScreenMode::Alt => self.altscreen.dump_contents_into(buf, dump_region),
332 }
333
334 let controls = term::control_codes();
335
336 controls.clear_attrs.term_input_into(buf);
339 let codes = term::Attrs::default().transition_to(&self.cursor_attrs);
340 for c in codes.into_iter() {
341 c.term_input_into(buf);
342 }
343 if self.cursor_style != term::CursorStyle::Default {
344 self.cursor_style.term_input_into(buf);
345 }
346
347 match (self.title_stack.last(), self.icon_name_stack.last()) {
352 (Some(title), Some(icon_name)) if !title.is_empty() && title == icon_name => {
353 ControlCodes::set_title_and_icon_name(title.clone()).term_input_into(buf)
354 }
355 (Some(title), Some(icon_name)) => {
356 if !title.is_empty() {
357 ControlCodes::set_title(title.clone()).term_input_into(buf);
358 }
359 if !icon_name.is_empty() {
360 ControlCodes::set_icon_name(icon_name.clone()).term_input_into(buf);
361 }
362 }
363 (Some(title), None) => {
364 if !title.is_empty() {
365 ControlCodes::set_title(title.clone()).term_input_into(buf);
366 }
367 }
368 (None, Some(icon_name)) => {
369 if !icon_name.is_empty() {
370 ControlCodes::set_icon_name(icon_name.clone()).term_input_into(buf);
371 }
372 }
373 (None, None) => {}
374 }
375
376 if let Some(working_dir) = &self.working_dir {
377 ControlCodes::set_working_dir(working_dir.host.clone(), working_dir.dir.clone())
378 .term_input_into(buf);
379 }
380
381 if !self.palette_overrides.is_empty() {
382 ControlCodes::set_color_indices(
383 self.palette_overrides
384 .iter()
385 .map(|(idx, color_spec)| (*idx, SmallVec::from(color_spec.as_slice()))),
386 )
387 .term_input_into(buf);
388 }
389
390 if self.cursor_hidden {
391 controls.hide_cursor.term_input_into(buf);
392 }
393 if self.application_keypad_mode_enabled {
394 controls.enable_application_keypad_mode.term_input_into(buf);
395 }
396 if self.report_focus {
397 controls.enable_report_focus.term_input_into(buf);
398 }
399 if self.in_paste_mode {
400 controls.enable_paste_mode.term_input_into(buf);
401 }
402
403 let mut functional_color_idx = 0;
406 while functional_color_idx < self.functional_colors.len() {
407 if let Some(color_spec) = &self.functional_colors[functional_color_idx] {
408 let start_idx = functional_color_idx;
409 let mut color_specs = vec![color_spec.as_slice()];
410
411 functional_color_idx += 1;
412 while functional_color_idx < self.functional_colors.len() {
413 if let Some(s) = &self.functional_colors[functional_color_idx] {
414 color_specs.push(s.as_slice());
415 } else {
416 break;
417 }
418 functional_color_idx += 1;
419 }
420
421 ControlCodes::set_functional_color(start_idx, color_specs).term_input_into(buf);
422 }
423
424 functional_color_idx += 1;
425 }
426 }
427
428 fn set_functional_color<'a, I>(&mut self, mut idx: usize, mut params_iter: I)
431 where
432 I: Iterator<Item = &'a &'a [u8]>,
433 {
434 while let Some(color_spec) = params_iter.next() {
435 if idx >= self.functional_colors.len() {
436 return;
437 }
438
439 if *color_spec != [b'?'] {
440 self.functional_colors[idx] = Some(Vec::from(*color_spec));
441 }
442
443 idx += 1;
444 }
445 }
446
447 fn set_title(&mut self, title: SmallVec<[u8; 8]>) {
448 if let Some(top) = self.title_stack.last_mut() {
449 *top = title;
450 } else {
451 self.title_stack.push(title);
452 }
453 }
454
455 fn set_icon_name(&mut self, icon_name: SmallVec<[u8; 8]>) {
456 if let Some(top) = self.icon_name_stack.last_mut() {
457 *top = icon_name;
458 } else {
459 self.icon_name_stack.push(icon_name);
460 }
461 }
462}
463
464enum ScreenMode {
466 Scrollback,
467 Alt,
468}
469
470impl vte::Perform for State {
471 fn print(&mut self, c: char) {
472 trace!("print: {}", c);
473 self.last_print_char = Some(c);
474 let attrs = self.cursor_attrs.clone();
475 let screen = self.screen_mut();
476 screen.snap_to_bottom();
477 if let Err(e) = screen.write_at_cursor(Cell::new(c, attrs)) {
478 warn!("writing char at cursor: {e:?}");
479 }
480 }
481
482 fn execute(&mut self, byte: u8) {
483 self.last_print_char = None;
484 trace!("execute: byte {}", byte);
485 match byte {
486 b'\n' => {
487 let screen = self.screen_mut();
488 let (scroll_top, scroll_bottom) =
489 screen.scroll_region(false).as_region(&screen.size).row_bounds();
490 let within_scroll =
491 scroll_top <= screen.cursor.row && screen.cursor.row < scroll_bottom;
492 screen.cursor.row += 1;
493 if within_scroll {
494 if screen.cursor.row >= scroll_bottom {
495 screen.scroll_down(1);
496 screen.cursor.row -= 1;
497 }
498 } else {
499 screen.clamp();
500 }
501 }
502 b'\r' => self.screen_mut().cursor.col = 0,
503 b'\t' => {
504 let mut col = self.screen().cursor.col;
505 col += 1;
506 while col < self.tabstops.len() && !self.tabstops.get(col).is_some_and(|b| *b) {
507 col += 1;
508 }
509
510 let screen = self.screen_mut();
511 screen.cursor.col = col;
512 screen.clamp();
513 }
514 b'\x08' => {
515 let screen = self.screen_mut();
517 screen.cursor.col = screen.cursor.col.saturating_sub(1);
518 }
519 b'\x07' => {}
521 _ => {
522 warn!("execute: unhandled byte {}", byte);
523 }
524 }
525 }
526
527 fn hook(&mut self, _params: &vte::Params, intermediates: &[u8], ignore: bool, action: char) {
528 self.last_print_char = None;
529 debug!(
530 "unhandled hook{}: {intermediates:?} {action}",
531 if ignore { " (ignored)" } else { "" }
532 );
533 }
534
535 fn put(&mut self, byte: u8) {
536 trace!("unhandled put: {byte}");
537 self.last_print_char = None;
538 }
539
540 fn unhook(&mut self) {
541 debug!("unhandled unhook");
542 self.last_print_char = None;
543 }
544
545 #[rustfmt::skip]
554 fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
555 trace!("osc_dispatch: {:?}", params);
556 self.last_print_char = None;
557
558 let mut params_iter = params.iter();
559 match params_iter.next() {
560 Some([b'0']) => if let Some(title) = params_iter.next() {
562 let title: SmallVec<[u8; 8]> = title.to_vec().into();
563 self.set_title(title.clone());
564 self.set_icon_name(title);
565 } else {
566 warn!("OSC 0 with no title param");
567 },
568 Some([b'1']) => if let Some(icon_name) = params_iter.next() {
569 let icon_name: SmallVec<[u8; 8]> = icon_name.to_vec().into();
570 self.set_icon_name(icon_name);
571 } else {
572 warn!("OSC 1 with no icon_name param");
573 },
574 Some([b'2']) => if let Some(title) = params_iter.next() {
575 let title: SmallVec<[u8; 8]> = title.to_vec().into();
576 self.set_title(title);
577 } else {
578 warn!("OSC 2 with no title param");
579 },
580
581 Some([b'4']) => while let (Some(idx), Some(color_spec)) = (params_iter.next(), params_iter.next()) {
583 if *color_spec == [b'?'] {
584 continue;
588 }
589
590 match std::str::from_utf8(idx) {
591 Ok(s) => match s.parse::<usize>() {
592 Ok(i) => {
593 self.palette_overrides.insert(i, color_spec.to_vec());
594 },
595 Err(e) => warn!("OSC 4: idx is an invalid number '{s}': {e}"),
596 },
597 Err(e) => warn!("OSC 4: invalid idx '{idx:?}': {e}"),
598 }
599 },
600 Some([b'1', b'0', b'4']) => while let Some(idx) = params_iter.next() {
601 match std::str::from_utf8(idx) {
602 Ok(s) => match s.parse::<usize>() {
603 Ok(i) => {
604 self.palette_overrides.remove(&i);
605 },
606 Err(e) => warn!("OSC 104: idx is an invalid number '{s}': {e}"),
607 },
608 Err(e) => warn!("OSC 104: invalid idx '{idx:?}': {e}"),
609 }
610 },
611
612 Some([b'7']) => if let (Some(host), Some(dir)) = (params_iter.next(), params_iter.next()) {
614 self.working_dir = Some(WorkingDir {
615 host: host.to_vec().into(),
616 dir: dir.to_vec().into(),
617 });
618 } else {
619 warn!("OSC 7 with fewer than 2 params");
620 },
621
622 Some([b'8']) => if let (Some(params), Some(url)) = (params_iter.next(), params_iter.next()) {
624 if params.is_empty() && url.is_empty() {
625 self.cursor_attrs.link_target = None;
626 } else {
627 self.cursor_attrs.link_target = Some(LinkTarget {
628 params: SmallVec::from_slice(params),
629 url: SmallVec::from_slice(url),
630 });
631 }
632 } else {
633 self.cursor_attrs.link_target = None;
634 },
635
636 Some([b'1', x]) if b'0' <= *x && *x <= b'9' =>
638 self.set_functional_color((*x - b'0') as usize, params_iter),
639
640 Some([b'5', b'2']) => debug!("ignoring OSC 52 (clipboard)"),
641 Some([b'9']) => debug!("ignoring OSC 9 (desktop notification)"),
642 Some([b'7', b'7', b'7']) => debug!("ignoring OSC 777"),
643 Some([b'1', b'3', b'3']) => debug!("ignoring OSC 133 (iterm2 marks)"),
644 Some([b'3', b'0', b'0', b'8']) => debug!("ignoring OSC 3008 (systemd context signaling)"),
645
646 _ => warn!("unhandled 'OSC {:?} {}'", params, if bell_terminated {
647 "BEL"
648 } else {
649 "ST"
650 }),
651 }
652 }
653
654 #[rustfmt::skip]
661 fn csi_dispatch(
662 &mut self,
663 params: &vte::Params,
664 intermediates: &[u8],
665 ignore: bool,
666 action: char,
667 ) {
668 if ignore {
669 warn!("malformed CSI seq");
670 return;
671 }
672 if tracing::enabled!(tracing::Level::TRACE) {
673 trace!("csi_dispatch: intermediates={:?} params={:?} {}",
674 intermediates, params.iter().collect::<Vec<_>>(), action);
675 }
676
677 let mut params_iter = params.iter();
678
679 if action != 'b' || !intermediates.is_empty() {
680 self.last_print_char = None;
681 }
682
683 match action {
684 'A' => {
686 let n = param_or(&mut params_iter, 1) as usize;
687 let screen = self.screen_mut();
688 screen.cursor.row = screen.cursor.row.saturating_sub(n);
689 screen.clamp();
690 }
691 'B' => {
693 let n = param_or(&mut params_iter, 1) as usize;
694 let screen = self.screen_mut();
695 screen.cursor.row += n;
696 screen.clamp();
697 }
698 'C' => {
700 let n = param_or(&mut params_iter, 1) as usize;
701 let screen = self.screen_mut();
702 screen.cursor.col += n;
703 screen.clamp();
704 }
705 'D' => {
707 let n = param_or(&mut params_iter, 1) as usize;
708 let screen = self.screen_mut();
709 screen.cursor.col = screen.cursor.col.saturating_sub(n);
710 screen.clamp();
711 }
712 'E' => {
714 let n = param_or(&mut params_iter, 1) as usize;
715 let screen = self.screen_mut();
716 screen.cursor.row += n;
717 screen.cursor.col = 0;
718 screen.clamp();
719 }
720 'F' => {
722 let n = param_or(&mut params_iter, 1) as usize;
723 let screen = self.screen_mut();
724 screen.cursor.row = screen.cursor.row.saturating_sub(n);
725 screen.cursor.col = 0;
726 screen.clamp();
727 }
728 '`' | 'G' => {
731 let n = param_or(&mut params_iter, 1) as usize;
732 let n = n.saturating_sub(1); let screen = self.screen_mut();
735 screen.cursor.col = n;
736 screen.clamp();
737 }
738 'f' | 'H' => {
741 let row = param_or(&mut params_iter, 1) as usize;
743 let col = param_or(&mut params_iter, 1) as usize;
744 let screen = self.screen_mut();
745 screen.set_cursor(term::Pos { row, col });
746 screen.clamp();
747 }
748 'J' => while let Some(code) = params_iter.next() {
750 match code {
751 [] | [0] => self.screen_mut().erase_to_end(),
752 [1] => self.screen_mut().erase_from_start(),
753 [2] => self.screen_mut().erase(false),
754 [3] => self.screen_mut().erase(true),
755 _ => warn!("unhandled 'CSI {code:?} J'"),
756 }
757 }
758 'K' => while let Some(code) = params_iter.next() {
760 match code {
761 [] | [0] => {
762 let screen = self.screen_mut();
763 let col = screen.cursor.col;
764 if let Some(l) = screen.get_line_mut() {
765 l.erase(line::Section::ToEnd(col));
766 }
767 }
768 [1] => {
769 let screen = self.screen_mut();
770 let col = screen.cursor.col;
771 if let Some(l) = screen.get_line_mut() {
772 l.erase(line::Section::StartTo(col));
773 }
774 }
775 [2] => if let Some(l) = self.screen_mut().get_line_mut() {
776 l.erase(line::Section::Whole);
777 }
778 _ => warn!("unhandled 'CSI {code:?} K'"),
779 }
780 }
781 'L' => {
783 let n = param_or(&mut params_iter, 1) as usize;
784 self.screen_mut().insert_lines(n);
785 }
786 'M' => {
788 let n = param_or(&mut params_iter, 1) as usize;
789 self.screen_mut().delete_lines(n);
790 }
791 'S' => {
793 let n = param_or(&mut params_iter, 1) as usize;
794 self.screen_mut().scroll_up(n as usize);
795 }
796 'W' => {
798 let code = param_or(&mut params_iter, 0) as usize;
799 match code {
800 0 => {
801 let col = self.screen().cursor.col;
802 self.tabstops.set(col, true);
803 },
804 2 => {
805 let col = self.screen().cursor.col;
806 self.tabstops.set(col, false);
807 }
808 5 => {
809 self.tabstops.fill(false);
810 }
811 _ => warn!("unhandled 'CSI {code:?} W'"),
812 }
813 }
814 'T' => {
816 let n = param_or(&mut params_iter, 1) as usize;
817 self.screen_mut().scroll_down(n as usize);
818 }
819
820 '@' => {
822 let n = param_or(&mut params_iter, 1) as usize;
823
824 let screen = self.screen_mut();
825 let width = screen.size.width;
826 let col = screen.cursor.col;
827 if let Some(l) = screen.get_line_mut() {
828 l.insert_character(width, col, n);
829 }
830 }
831 'P' => {
833 let n = param_or(&mut params_iter, 1) as usize;
834
835 let attrs = self.cursor_attrs.clone();
836
837 let screen = self.screen_mut();
838 let width = screen.size.width;
839 let col = screen.cursor.col;
840 if let Some(l) = screen.get_line_mut() {
841 l.delete_character(width, col, &attrs, n);
842 }
843 }
844 'X' => {
846 let n = param_or(&mut params_iter, 1) as usize;
847
848 let attrs = self.cursor_attrs.clone();
849
850 let screen = self.screen_mut();
851 let width = screen.size.width;
852 let col = screen.cursor.col;
853 if let Some(l) = screen.get_line_mut() {
854 l.erase_character(width, col, &attrs, n);
855 }
856 }
857 'b' if intermediates.is_empty() => if let Some(c) = self.last_print_char {
859 let n = param_or(&mut params_iter, 1) as usize;
860
861 let cell = Cell::new(c, self.cursor_attrs.clone());
862 let screen = self.screen_mut();
863 screen.snap_to_bottom();
864 for _ in 0..n {
865 if let Err(e) = screen.write_at_cursor(cell.clone()) {
866 warn!("writing char at cursor: {e:?}");
867 }
868 }
869 }
870 'c' => debug!("CSI ... c - device attribute query"),
871 'd' => {
873 let row = param_or(&mut params_iter, 1) as usize;
874 let col = self.screen().cursor.col + 1;
875 let screen = self.screen_mut();
876 screen.set_cursor(term::Pos { row, col });
877 screen.clamp();
878 }
879
880 's' => {
882 let screen = self.screen_mut();
883 let cursor = screen.cursor.clone();
884 screen.saved_cursor.pos = cursor;
885 }
886 't' => while let Some(code) = params_iter.next() {
888 match code {
889 [14] => debug!("CSI 14 t - pixel size query"),
890 [16] => debug!("CSI 16 t - cell size query"),
891 [18] => debug!("CSI 18 t - term size query"),
892 [19] => debug!("CSI 19 t - display size query"),
893 [22] => {
894 let code = param_or(&mut params_iter, 0) as usize;
895 if (code == 0 || code == 1) && self.icon_name_stack.len() < MAX_TITLE_STACK_DEPTH {
896 if let Some(icon_name) = self.icon_name_stack.last().cloned() {
897 self.icon_name_stack.push(icon_name);
898 } else {
899 self.icon_name_stack.push(SmallVec::new());
900 }
901 }
902
903 if (code == 0 || code == 2) && self.title_stack.len() < MAX_TITLE_STACK_DEPTH {
904 if let Some(title) = self.title_stack.last().cloned() {
905 self.title_stack.push(title);
906 } else {
907 self.title_stack.push(SmallVec::new());
908 }
909 }
910 }
911 [23] => {
912 let code = param_or(&mut params_iter, 0) as usize;
913 if code == 0 || code == 1 {
914 self.icon_name_stack.pop();
915 }
916
917 if code == 0 || code == 2 {
918 self.title_stack.pop();
919 }
920 }
921 _ => warn!("unhandled CSI ... {:?} t", code),
922 }
923 }
924 'u' => {
926 let screen = self.screen_mut();
927 screen.cursor = screen.saved_cursor.pos;
928 screen.clamp();
929 }
930
931 'g' => {
933 let code = param_or(&mut params_iter, 0) as usize;
934 match code {
935 0 => {
936 let col = self.screen().cursor.col;
937 self.tabstops.set(col, false);
938 },
939 3 => {
940 self.tabstops.fill(false);
941 }
942 _ => warn!("unhandled 'CSI {code:?} g'"),
943 }
944 }
945
946 'h' => match intermediates {
947 [b'?'] => while let Some(code) = params_iter.next() {
948 match code {
949 [1] => self.application_keypad_mode_enabled = true,
950 [6] => self.screen_mut().set_origin_mode(OriginMode::ScrollRegion),
951 [25] => self.cursor_hidden = false,
952 [1004] => self.report_focus = true,
953 [1049] => {
955 self.altscreen = Screen::alt(self.altscreen.size);
958 self.screen_mode = ScreenMode::Alt;
959 }
960 [2004] => self.in_paste_mode = true,
961 [2026] => {},
964
965 _ => {
966 warn!(
967 "Unhandled CSI h command: CSI {:?} {:?} h",
968 intermediates,
969 params.iter().collect::<Vec<&[u16]>>()
970 );
971 return;
972 }
973 }
974 }
975 _ => warn!(
976 "Unhandled CSI h command: CSI {:?} {:?} h",
977 intermediates,
978 params.iter().collect::<Vec<&[u16]>>()
979 ),
980 }
981 'l' => match intermediates {
982 [b'?'] => while let Some(code) = params_iter.next() {
983 match code {
984 [1] => self.application_keypad_mode_enabled = false,
985 [6] => self.screen_mut().set_origin_mode(OriginMode::Term),
986 [25] => self.cursor_hidden = true,
987 [1004] => self.report_focus = false,
988 [1049] => self.screen_mode = ScreenMode::Scrollback,
989 [2004] => self.in_paste_mode = false,
990 [2026] => {},
993 _ => {
994 warn!(
995 "Unhandled CSI l command: CSI {:?} {:?} l",
996 intermediates,
997 params.iter().collect::<Vec<&[u16]>>()
998 );
999 return;
1000 }
1001 }
1002 }
1003 _ => warn!(
1004 "Unhandled CSI l command: CSI {:?} {:?} l",
1005 intermediates,
1006 params.iter().collect::<Vec<&[u16]>>()
1007 ),
1008 },
1009 'n' => while let Some(param) = params_iter.next() {
1011 match param {
1012 [6] => debug!("ignoring DSR (CSI 6 n), that's the real terminal's job"),
1021 _ => {}
1022 }
1023 },
1024
1025 'm' => while let Some(param) = params_iter.next() {
1027 match param {
1028 [] | [0] => self.cursor_attrs = term::Attrs::default(),
1029
1030 [4] => self.cursor_attrs.underline = Some(UnderlineStyle::Single),
1041 [21] => self.cursor_attrs.underline = Some(UnderlineStyle::Double),
1042 [24] => self.cursor_attrs.underline = None,
1043
1044 [1] => self.cursor_attrs.font_weight = Some(FontWeight::Bold),
1046 [2] => self.cursor_attrs.font_weight = Some(FontWeight::Faint),
1047 [22] => self.cursor_attrs.font_weight = None,
1048
1049 [3] => self.cursor_attrs.italic = true,
1051 [23] => self.cursor_attrs.italic = false,
1052
1053 [7] => self.cursor_attrs.inverse = true,
1055 [27] => self.cursor_attrs.inverse = false,
1056
1057 [5] => self.cursor_attrs.blink = Some(BlinkStyle::Slow),
1059 [6] => self.cursor_attrs.blink = Some(BlinkStyle::Rapid),
1060 [25] => self.cursor_attrs.blink = None,
1061
1062 [8] => self.cursor_attrs.conceal = true,
1064 [28] => self.cursor_attrs.conceal = false,
1065
1066 [9] => self.cursor_attrs.strikethrough = true,
1068 [29] => self.cursor_attrs.strikethrough = false,
1069
1070 [51] => self.cursor_attrs.framed = Some(FrameStyle::Frame),
1072 [52] => self.cursor_attrs.framed = Some(FrameStyle::Circle),
1073 [54] => self.cursor_attrs.framed = None,
1074
1075 [53] => self.cursor_attrs.overline = true,
1077 [55] => self.cursor_attrs.overline = false,
1078
1079 [49] => self.cursor_attrs.bgcolor = term::Color::Default,
1081 [n] if 40 <= *n && *n < 48 => match (*n - 40).try_into() {
1082 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
1083 Err(e) => warn!("out of bounds bgcolor idx (1): {e:?}"),
1084 }
1085 [n] if 100 <= *n && *n < 108 => match (*n - 92).try_into() {
1086 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
1087 Err(e) => warn!("out of bounds bgcolor idx (2): {e:?}"),
1088 }
1089 [48] => match params_iter.next() {
1090 Some([5]) => {
1091 let n = param_or(&mut params_iter, 0);
1092 match n.try_into() {
1093 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
1094 Err(e) => warn!("out of bounds bgcolor idx (3): {e:?}"),
1095 }
1096 },
1097 Some([2]) => {
1098 let r = param_or(&mut params_iter, 0);
1104 let g = param_or(&mut params_iter, 0);
1105 let b = param_or(&mut params_iter, 0);
1106 if let (Ok(r), Ok(g), Ok(b)) = (r.try_into(), g.try_into(), b.try_into()) {
1107 self.cursor_attrs.bgcolor = term::Color::Rgb(r, g, b);
1108 } else {
1109 warn!("out of bounds color codes for CSI 48 2 ... m");
1110 }
1111 },
1112 _ => warn!("unhandled incomplete 'CSI 48 ... m'"),
1113 },
1114
1115 [39] => self.cursor_attrs.fgcolor = term::Color::Default,
1117 [n] if 30 <= *n && *n < 38 => match (*n - 30).try_into() {
1118 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
1119 Err(e) => warn!("out of bounds fgcolor idx (1): {e:?}"),
1120 }
1121 [n] if 90 <= *n && *n < 98 => match (*n - 82).try_into() {
1122 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
1123 Err(e) => warn!("out of bounds fgcolor idx (2): {e:?}"),
1124 }
1125 [38] => match params_iter.next() {
1126 Some([5]) => {
1127
1128 let n = param_or(&mut params_iter, 0);
1129 match n.try_into() {
1130 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
1131 Err(e) => warn!("out of bounds fgcolor idx (3): {e:?}"),
1132 }
1133 },
1134 Some([2]) => {
1135 let r = param_or(&mut params_iter, 0);
1141 let g = param_or(&mut params_iter, 0);
1142 let b = param_or(&mut params_iter, 0);
1143 if let (Ok(r), Ok(g), Ok(b)) = (r.try_into(), g.try_into(), b.try_into()) {
1144 self.cursor_attrs.fgcolor = term::Color::Rgb(r, g, b);
1145 } else {
1146 warn!("out of bounds color codes for CSI 38 2 ... m");
1147 }
1148 },
1149 _ => warn!("unhandled incomplete 'CSI 38 ... m'"),
1150 },
1151
1152 _ => warn!("unhandled 'CSI {param:?} m'"),
1153 }
1154 }
1155 'p' => match intermediates {
1156 [b'!'] => {
1158 self.tabstops.fill(false);
1159 let width = self.screen().size.width;
1160 self.fill_tabstops(0, width);
1161 self.cursor_style = term::CursorStyle::Default;
1162
1163 warn!("DECSTR only partially handled");
1164 }
1165 [b'?', b'$'] => {
1167 debug!("ignoring DECRQM query: params={:?}", params.iter().collect::<Vec<_>>());
1179 }
1180 _ => warn!(
1181 "Unhandled CSI p command: CSI {:?} {:?} p",
1182 intermediates,
1183 params.iter().collect::<Vec<&[u16]>>()
1184 ),
1185 },
1186 'q' if intermediates == [b' '] => {
1188 let code = param_or(&mut params_iter, 0) as usize;
1189 match term::CursorStyle::try_from(code) {
1190 Ok(style) => self.cursor_style = style,
1191 Err(e) => warn!("parsing cursor style: {:?}", e),
1192 }
1193 },
1194 'r' => {
1196 let top = maybe_param(&mut params_iter);
1197 let bottom = maybe_param(&mut params_iter);
1198
1199 let screen = self.screen_mut();
1200 screen.set_scroll_region(match (top, bottom) {
1201 (None, None) => term::ScrollRegion::TrackSize,
1202 (Some(t), None) => term::ScrollRegion::Window {
1203 top: t.saturating_sub(1) as usize,
1204 bottom: screen.size.height,
1205 },
1206 (None, Some(b)) => term::ScrollRegion::Window {
1207 top: 0,
1208 bottom: b as usize,
1209 },
1210 (Some(t), Some(b)) => term::ScrollRegion::Window {
1211 top: t.saturating_sub(1) as usize,
1212 bottom: b as usize,
1213 }
1214 });
1215 }
1216
1217 _ => {
1218 warn!("unhandled action {}", action);
1219 }
1220 }
1221 }
1222
1223 fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
1224 if ignore {
1225 warn!("malformed ESC seq");
1226 return;
1227 }
1228 trace!("esc_dispatch: {}", byte);
1229 self.last_print_char = None;
1230
1231 match (intermediates, byte) {
1232 ([], b'7') => {
1234 let attrs = self.cursor_attrs.clone();
1235 let screen = self.screen_mut();
1236 let pos = screen.cursor.clone();
1237 screen.saved_cursor = SavedCursor { pos, attrs };
1238 }
1239 ([], b'8') => {
1241 let screen = self.screen_mut();
1242 screen.cursor = screen.saved_cursor.pos;
1243 self.cursor_attrs = screen.saved_cursor.attrs.clone();
1244 }
1245 ([], b'H') => {
1247 let col = self.screen().cursor.col;
1248 self.tabstops.set(col, true);
1249 }
1250 ([], b'c') => {
1252 self.tabstops.fill(false);
1253 let width = self.screen().size.width;
1254 self.fill_tabstops(0, width);
1255 self.cursor_style = term::CursorStyle::Default;
1256
1257 warn!("RIS only partially handled");
1258 }
1259
1260 ([], b'=') => self.application_keypad_mode_enabled = true,
1261 ([], b'>') => self.application_keypad_mode_enabled = false,
1262
1263 ([b'(' | b')' | b'*' | b'+'], b'B' | b'A') => {}
1266
1267 ([], 92) => {}
1270
1271 _ => warn!("unhandled ESC seq ({intermediates:?}, {byte})"),
1272 }
1273 }
1274
1275 fn terminated(&self) -> bool {
1276 false
1277 }
1278}
1279
1280fn param_or<'params>(params: &mut vte::ParamsIter<'params>, default: u16) -> u16 {
1281 maybe_param(params).unwrap_or(default)
1282}
1283
1284fn maybe_param<'params>(params: &mut vte::ParamsIter<'params>) -> Option<u16> {
1285 match params.next() {
1286 Some([0]) => None,
1287 Some([p]) => Some(*p),
1288 _ => None,
1289 }
1290}
1291
1292const NONE_VEC: Option<Vec<u8>> = None;