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
45pub struct Term {
47 parser: vte::Parser,
48 state: State,
49}
50
51impl Term {
52 pub fn new(scrollback_lines: usize, size: Size) -> Self {
61 Term { parser: vte::Parser::new(), state: State::new(scrollback_lines, size) }
62 }
63
64 pub fn size(&self) -> Size {
66 self.state.screen().size
67 }
68
69 pub fn resize(&mut self, size: Size) {
74 if size.height > self.scrollback_lines() {
75 self.set_scrollback_lines(size.height);
76 }
77
78 self.state.resize(size);
79 }
80
81 pub fn scrollback_lines(&self) -> usize {
83 self.state.scrollback.scrollback_lines().expect("scrollback screen to have lines")
84 }
85
86 pub fn set_scrollback_lines(&mut self, scrollback_lines: usize) {
94 self.state.scrollback.set_scrollback_lines(scrollback_lines);
95 }
96
97 pub fn process(&mut self, buf: &[u8]) {
100 self.parser.advance(&mut self.state, buf);
101 }
102
103 pub fn contents(&self, dump_region: ContentRegion) -> Vec<u8> {
108 let mut buf = vec![];
109 term::control_codes().clear_attrs.term_input_into(&mut buf);
110 term::ControlCodes::cursor_position(1, 1).term_input_into(&mut buf);
111 term::control_codes().clear_screen.term_input_into(&mut buf);
112 self.state.dump_contents_into(&mut buf, dump_region);
113
114 buf
115 }
116}
117
118#[derive(Debug, Eq, PartialEq, Clone)]
120pub enum ContentRegion {
121 All,
123 Screen,
125 BottomLines(usize),
127}
128
129impl std::fmt::Display for Term {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 self.state.fmt(f)
132 }
133}
134
135#[derive(Debug, Clone, Copy, Eq, PartialEq)]
137pub struct Size {
138 pub width: usize,
139 pub height: usize,
140}
141
142struct State {
144 scrollback: Screen,
146 altscreen: Screen,
148 screen_mode: ScreenMode,
150 cursor_attrs: term::Attrs,
154 title: Option<SmallVec<[u8; 8]>>,
156 icon_name: Option<SmallVec<[u8; 8]>>,
158 working_dir: Option<WorkingDir>,
161 palette_overrides: BTreeMap<usize, Vec<u8>>,
165 functional_colors: [Option<Vec<u8>>; 10],
168 cursor_hidden: bool,
171 application_keypad_mode_enabled: bool,
174 in_paste_mode: bool,
176 tabstops: BitVec,
180}
181
182struct WorkingDir {
183 host: SmallVec<[u8; 8]>,
184 dir: SmallVec<[u8; 8]>,
185}
186
187impl std::fmt::Display for State {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 match self.screen_mode {
190 ScreenMode::Scrollback => {
191 writeln!(f, "Screen Mode: Scrollback")?;
192 write!(f, "{}", self.scrollback)?;
193 }
194 ScreenMode::Alt => {
195 writeln!(f, "Screen Mode: AltScreen")?;
196 write!(f, "{}", self.altscreen)?;
197 }
198 }
199
200 Ok(())
201 }
202}
203
204impl State {
205 fn new(scrollback_lines: usize, size: Size) -> Self {
206 let mut st = State {
207 scrollback: Screen::scrollback(scrollback_lines, size),
208 altscreen: Screen::alt(size),
209 screen_mode: ScreenMode::Scrollback,
210 cursor_attrs: term::Attrs::default(),
211 title: None,
212 icon_name: None,
213 working_dir: None,
214 palette_overrides: BTreeMap::new(),
215 functional_colors: [NONE_VEC; 10],
216 cursor_hidden: false,
217 application_keypad_mode_enabled: false,
218 in_paste_mode: false,
219 tabstops: bitvec![0; size.width],
220 };
221 st.fill_tabstops(0, size.width);
222 st
223 }
224
225 fn screen_mut(&mut self) -> &mut Screen {
226 match self.screen_mode {
227 ScreenMode::Scrollback => &mut self.scrollback,
228 ScreenMode::Alt => &mut self.altscreen,
229 }
230 }
231
232 fn screen(&self) -> &Screen {
233 match self.screen_mode {
234 ScreenMode::Scrollback => &self.scrollback,
235 ScreenMode::Alt => &self.altscreen,
236 }
237 }
238
239 fn resize(&mut self, size: Size) {
240 let orig_len = self.tabstops.len();
241 self.tabstops.resize(size.width, false);
242 if size.width > orig_len {
243 self.fill_tabstops(orig_len, size.width);
244 }
245
246 self.scrollback.resize(size);
247 self.altscreen.resize(size);
248 }
249
250 fn fill_tabstops(&mut self, start: usize, end: usize) {
252 assert!(end <= self.tabstops.len());
253
254 for i in start..end {
255 if i > 0 && i % 8 == 0 {
256 self.tabstops.set(i, true);
257 }
258 }
259 }
260
261 fn dump_tabstops(&self, buf: &mut Vec<u8>) {
266 let controls = term::control_codes();
267 if self.tabstops.len() > 8 && self.tabstops.not_any() {
268 ControlCodes::tab_clear(Some(3)).term_input_into(buf);
271 return;
272 }
273
274 let mut codes = vec![];
275 for i in 0..self.tabstops.len() {
276 let bit = self.tabstops.get(i).is_some_and(|b| *b);
277 let i: u16 = match i.try_into() {
278 Ok(i) => i,
279 Err(e) => {
280 warn!("generating tabstop codes: index out of bounds: {:?}", e);
281 return;
282 }
283 };
284 if i > 0 && i % 8 == 0 {
285 if !bit {
287 codes.push(ControlCodes::cursor_position(1, i + 1));
288 codes.push(ControlCodes::tab_clear(None));
289 }
290 } else {
291 if bit {
293 codes.push(ControlCodes::cursor_position(1, i + 1));
294 codes.push(controls.horizontal_tab_set.clone());
295 }
296 }
297 }
298
299 if !codes.is_empty() {
300 for code in codes.into_iter() {
301 code.term_input_into(buf);
302 }
303 ControlCodes::cursor_position(1, 1).term_input_into(buf);
304 }
305 }
306
307 fn dump_contents_into(&self, buf: &mut Vec<u8>, dump_region: ContentRegion) {
308 self.dump_tabstops(buf);
309
310 match self.screen_mode {
311 ScreenMode::Scrollback => self.scrollback.dump_contents_into(buf, dump_region),
312 ScreenMode::Alt => self.altscreen.dump_contents_into(buf, dump_region),
313 }
314
315 let controls = term::control_codes();
316
317 controls.clear_attrs.term_input_into(buf);
320 let codes = term::Attrs::default().transition_to(&self.cursor_attrs);
321 for c in codes.into_iter() {
322 c.term_input_into(buf);
323 }
324
325 match (&self.title, &self.icon_name) {
330 (Some(title), Some(icon_name)) if title == icon_name => {
331 ControlCodes::set_title_and_icon_name(title.clone()).term_input_into(buf)
332 }
333 (Some(title), Some(icon_name)) => {
334 ControlCodes::set_title(title.clone()).term_input_into(buf);
335 ControlCodes::set_icon_name(icon_name.clone()).term_input_into(buf);
336 }
337 (Some(title), None) => {
338 ControlCodes::set_title(title.clone()).term_input_into(buf);
339 }
340 (None, Some(icon_name)) => {
341 ControlCodes::set_icon_name(icon_name.clone()).term_input_into(buf);
342 }
343 (None, None) => {}
344 }
345
346 if let Some(working_dir) = &self.working_dir {
347 ControlCodes::set_working_dir(working_dir.host.clone(), working_dir.dir.clone())
348 .term_input_into(buf);
349 }
350
351 if !self.palette_overrides.is_empty() {
352 ControlCodes::set_color_indices(
353 self.palette_overrides
354 .iter()
355 .map(|(idx, color_spec)| (*idx, SmallVec::from(color_spec.as_slice()))),
356 )
357 .term_input_into(buf);
358 }
359
360 if self.cursor_hidden {
361 controls.hide_cursor.term_input_into(buf);
362 }
363 if self.application_keypad_mode_enabled {
364 controls.enable_application_keypad_mode.term_input_into(buf);
365 }
366 if self.in_paste_mode {
367 controls.enable_paste_mode.term_input_into(buf);
368 }
369
370 let mut functional_color_idx = 0;
373 while functional_color_idx < self.functional_colors.len() {
374 if let Some(color_spec) = &self.functional_colors[functional_color_idx] {
375 let start_idx = functional_color_idx;
376 let mut color_specs = vec![color_spec.as_slice()];
377
378 functional_color_idx += 1;
379 while functional_color_idx < self.functional_colors.len() {
380 if let Some(s) = &self.functional_colors[functional_color_idx] {
381 color_specs.push(s.as_slice());
382 } else {
383 break;
384 }
385 functional_color_idx += 1;
386 }
387
388 ControlCodes::set_functional_color(start_idx, color_specs).term_input_into(buf);
389 }
390
391 functional_color_idx += 1;
392 }
393 }
394
395 fn set_functional_color<'a, I>(&mut self, mut idx: usize, mut params_iter: I)
398 where
399 I: Iterator<Item = &'a &'a [u8]>,
400 {
401 while let Some(color_spec) = params_iter.next() {
402 if idx >= self.functional_colors.len() {
403 return;
404 }
405
406 if *color_spec != [b'?'] {
407 self.functional_colors[idx] = Some(Vec::from(*color_spec));
408 }
409
410 idx += 1;
411 }
412 }
413}
414
415enum ScreenMode {
417 Scrollback,
418 Alt,
419}
420
421impl vte::Perform for State {
422 fn print(&mut self, c: char) {
423 trace!("print: {}", c);
424 let attrs = self.cursor_attrs.clone();
425 let screen = self.screen_mut();
426 screen.snap_to_bottom();
427 if let Err(e) = screen.write_at_cursor(Cell::new(c, attrs)) {
428 warn!("writing char at cursor: {e:?}");
429 }
430 }
431
432 fn execute(&mut self, byte: u8) {
433 trace!("execute: byte {}", byte);
434 match byte {
435 b'\n' => {
436 let screen = self.screen_mut();
437 let (scroll_top, scroll_bottom) =
438 screen.scroll_region(false).as_region(&screen.size).row_bounds();
439 let within_scroll =
440 scroll_top <= screen.cursor.row && screen.cursor.row < scroll_bottom;
441 screen.cursor.row += 1;
442 if within_scroll {
443 if screen.cursor.row >= scroll_bottom {
444 screen.scroll_down(1);
445 screen.cursor.row -= 1;
446 }
447 } else {
448 screen.clamp();
449 }
450 }
451 b'\r' => self.screen_mut().cursor.col = 0,
452 b'\t' => {
453 let mut col = self.screen().cursor.col;
454 col += 1;
455 while col < self.tabstops.len() && !self.tabstops.get(col).is_some_and(|b| *b) {
456 col += 1;
457 }
458
459 let screen = self.screen_mut();
460 screen.cursor.col = col;
461 screen.clamp();
462 }
463 b'\x08' => {
464 let screen = self.screen_mut();
466 screen.cursor.col = screen.cursor.col.saturating_sub(1);
467 }
468 b'\x07' => {}
470 _ => {
471 warn!("execute: unhandled byte {}", byte);
472 }
473 }
474 }
475
476 fn hook(&mut self, _params: &vte::Params, intermediates: &[u8], ignore: bool, action: char) {
477 debug!(
478 "unhandled hook{}: {intermediates:?} {action}",
479 if ignore { " (ignored)" } else { "" }
480 );
481 }
482
483 fn put(&mut self, byte: u8) {
484 trace!("unhandled put: {byte}");
485 }
486
487 fn unhook(&mut self) {
488 debug!("unhandled unhook");
489 }
490
491 #[rustfmt::skip]
500 fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
501 trace!("osc_dispatch: {:?}", params);
502
503 let mut params_iter = params.iter();
504 match params_iter.next() {
505 Some([b'0']) => if let Some(title) = params_iter.next() {
507 self.title = Some(title.to_vec().into());
508 self.icon_name = Some(title.to_vec().into());
509 } else {
510 warn!("OSC 0 with no title param");
511 },
512 Some([b'1']) => if let Some(icon_name) = params_iter.next() {
513 self.icon_name = Some(icon_name.to_vec().into());
514 } else {
515 warn!("OSC 1 with no icon_name param");
516 },
517 Some([b'2']) => if let Some(title) = params_iter.next() {
518 self.title = Some(title.to_vec().into());
519 } else {
520 warn!("OSC 2 with no title param");
521 },
522
523 Some([b'4']) => while let (Some(idx), Some(color_spec)) = (params_iter.next(), params_iter.next()) {
525 if *color_spec == [b'?'] {
526 continue;
530 }
531
532 match std::str::from_utf8(idx) {
533 Ok(s) => match s.parse::<usize>() {
534 Ok(i) => {
535 self.palette_overrides.insert(i, color_spec.to_vec());
536 },
537 Err(e) => warn!("OSC 4: idx is an invalid number '{s}': {e}"),
538 },
539 Err(e) => warn!("OSC 4: invalid idx '{idx:?}': {e}"),
540 }
541 },
542 Some([b'1', b'0', b'4']) => while let Some(idx) = params_iter.next() {
543 match std::str::from_utf8(idx) {
544 Ok(s) => match s.parse::<usize>() {
545 Ok(i) => {
546 self.palette_overrides.remove(&i);
547 },
548 Err(e) => warn!("OSC 104: idx is an invalid number '{s}': {e}"),
549 },
550 Err(e) => warn!("OSC 104: invalid idx '{idx:?}': {e}"),
551 }
552 },
553
554 Some([b'7']) => if let (Some(host), Some(dir)) = (params_iter.next(), params_iter.next()) {
556 self.working_dir = Some(WorkingDir {
557 host: host.to_vec().into(),
558 dir: dir.to_vec().into(),
559 });
560 } else {
561 warn!("OSC 7 with fewer than 2 params");
562 },
563
564 Some([b'8']) => if let (Some(params), Some(url)) = (params_iter.next(), params_iter.next()) {
566 if params.is_empty() && url.is_empty() {
567 self.cursor_attrs.link_target = None;
568 } else {
569 self.cursor_attrs.link_target = Some(LinkTarget {
570 params: SmallVec::from_slice(params),
571 url: SmallVec::from_slice(url),
572 });
573 }
574 } else {
575 self.cursor_attrs.link_target = None;
576 },
577
578 Some([b'1', x]) if b'0' <= *x && *x <= b'9' =>
580 self.set_functional_color((*x - b'0') as usize, params_iter),
581
582 Some([b'5', b'2']) => debug!("ignoring OSC 52 (clipboard)"),
583 Some([b'9']) => debug!("ignoring OSC 9 (desktop notification)"),
584 Some([b'7', b'7', b'7']) => debug!("ignoring OSC 777"),
585 Some([b'1', b'3', b'3']) => debug!("ignoring OSC 133 (iterm2 marks)"),
586 Some([b'3', b'0', b'0', b'8']) => debug!("ignoring OSC 3008 (systemd context signaling)"),
587
588 _ => warn!("unhandled 'OSC {:?} {}'", params, if bell_terminated {
589 "BEL"
590 } else {
591 "ST"
592 }),
593 }
594 }
595
596 #[rustfmt::skip]
603 fn csi_dispatch(
604 &mut self,
605 params: &vte::Params,
606 intermediates: &[u8],
607 ignore: bool,
608 action: char,
609 ) {
610 if ignore {
611 warn!("malformed CSI seq");
612 return;
613 }
614 if tracing::enabled!(tracing::Level::TRACE) {
615 trace!("csi_dispatch: intermediates={:?} params={:?} {}",
616 intermediates, params.iter().collect::<Vec<_>>(), action);
617 }
618
619 let mut params_iter = params.iter();
620
621 match action {
622 'A' => {
624 let n = param_or(&mut params_iter, 1) as usize;
625 let screen = self.screen_mut();
626 screen.cursor.row = screen.cursor.row.saturating_sub(n);
627 screen.clamp();
628 }
629 'B' => {
631 let n = param_or(&mut params_iter, 1) as usize;
632 let screen = self.screen_mut();
633 screen.cursor.row += n;
634 screen.clamp();
635 }
636 'C' => {
638 let n = param_or(&mut params_iter, 1) as usize;
639 let screen = self.screen_mut();
640 screen.cursor.col += n;
641 screen.clamp();
642 }
643 'D' => {
645 let n = param_or(&mut params_iter, 1) as usize;
646 let screen = self.screen_mut();
647 screen.cursor.col = screen.cursor.col.saturating_sub(n);
648 screen.clamp();
649 }
650 'E' => {
652 let n = param_or(&mut params_iter, 1) as usize;
653 let screen = self.screen_mut();
654 screen.cursor.row += n;
655 screen.cursor.col = 0;
656 screen.clamp();
657 }
658 'F' => {
660 let n = param_or(&mut params_iter, 1) as usize;
661 let screen = self.screen_mut();
662 screen.cursor.row = screen.cursor.row.saturating_sub(n);
663 screen.cursor.col = 0;
664 screen.clamp();
665 }
666 'G' => {
668 let n = param_or(&mut params_iter, 1) as usize;
669 let n = n.saturating_sub(1); let screen = self.screen_mut();
672 screen.cursor.col = n;
673 screen.clamp();
674 }
675 'H' => {
677 let row = param_or(&mut params_iter, 1) as usize;
679 let col = param_or(&mut params_iter, 1) as usize;
680 let screen = self.screen_mut();
681 screen.set_cursor(term::Pos { row, col });
682 screen.clamp();
683 }
684 'J' => while let Some(code) = params_iter.next() {
686 match code {
687 [] | [0] => self.screen_mut().erase_to_end(),
688 [1] => self.screen_mut().erase_from_start(),
689 [2] => self.screen_mut().erase(false),
690 [3] => self.screen_mut().erase(true),
691 _ => warn!("unhandled 'CSI {code:?} J'"),
692 }
693 }
694 'K' => while let Some(code) = params_iter.next() {
696 match code {
697 [] | [0] => {
698 let screen = self.screen_mut();
699 let col = screen.cursor.col;
700 if let Some(l) = screen.get_line_mut() {
701 l.erase(line::Section::ToEnd(col));
702 }
703 }
704 [1] => {
705 let screen = self.screen_mut();
706 let col = screen.cursor.col;
707 if let Some(l) = screen.get_line_mut() {
708 l.erase(line::Section::StartTo(col));
709 }
710 }
711 [2] => if let Some(l) = self.screen_mut().get_line_mut() {
712 l.erase(line::Section::Whole);
713 }
714 _ => warn!("unhandled 'CSI {code:?} K'"),
715 }
716 }
717 'L' => {
719 let n = param_or(&mut params_iter, 1) as usize;
720 self.screen_mut().insert_lines(n);
721 }
722 'M' => {
724 let n = param_or(&mut params_iter, 1) as usize;
725 self.screen_mut().delete_lines(n);
726 }
727 'S' => {
729 let n = param_or(&mut params_iter, 1) as usize;
730 self.screen_mut().scroll_up(n as usize);
731 }
732 'W' => {
734 let code = param_or(&mut params_iter, 0) as usize;
735 match code {
736 0 => {
737 let col = self.screen().cursor.col;
738 self.tabstops.set(col, true);
739 },
740 2 => {
741 let col = self.screen().cursor.col;
742 self.tabstops.set(col, false);
743 }
744 5 => {
745 self.tabstops.fill(false);
746 }
747 _ => warn!("unhandled 'CSI {code:?} W'"),
748 }
749 }
750 'T' => {
752 let n = param_or(&mut params_iter, 1) as usize;
753 self.screen_mut().scroll_down(n as usize);
754 }
755
756 '@' => {
758 let n = param_or(&mut params_iter, 1) as usize;
759
760 let screen = self.screen_mut();
761 let width = screen.size.width;
762 let col = screen.cursor.col;
763 if let Some(l) = screen.get_line_mut() {
764 l.insert_character(width, col, n);
765 }
766 }
767 'P' => {
769 let n = param_or(&mut params_iter, 1) as usize;
770
771 let attrs = self.cursor_attrs.clone();
772
773 let screen = self.screen_mut();
774 let width = screen.size.width;
775 let col = screen.cursor.col;
776 if let Some(l) = screen.get_line_mut() {
777 l.delete_character(width, col, &attrs, n);
778 }
779 }
780 'X' => {
782 let n = param_or(&mut params_iter, 1) as usize;
783
784 let attrs = self.cursor_attrs.clone();
785
786 let screen = self.screen_mut();
787 let width = screen.size.width;
788 let col = screen.cursor.col;
789 if let Some(l) = screen.get_line_mut() {
790 l.erase_character(width, col, &attrs, n);
791 }
792 }
793
794 's' => {
796 let screen = self.screen_mut();
797 let cursor = screen.cursor.clone();
798 screen.saved_cursor.pos = cursor;
799 }
800 'u' => {
802 let screen = self.screen_mut();
803 screen.cursor = screen.saved_cursor.pos;
804 screen.clamp();
805 }
806
807 'g' => {
809 let code = param_or(&mut params_iter, 0) as usize;
810 match code {
811 0 => {
812 let col = self.screen().cursor.col;
813 self.tabstops.set(col, false);
814 },
815 3 => {
816 self.tabstops.fill(false);
817 }
818 _ => warn!("unhandled 'CSI {code:?} g'"),
819 }
820 }
821
822 'h' => match intermediates {
823 [b'?'] => while let Some(code) = params_iter.next() {
824 match code {
825 [1] => self.application_keypad_mode_enabled = true,
826 [6] => self.screen_mut().set_origin_mode(OriginMode::ScrollRegion),
827 [25] => self.cursor_hidden = false,
828 [1049] => {
830 self.altscreen = Screen::alt(self.altscreen.size);
833 self.screen_mode = ScreenMode::Alt;
834 }
835 [2004] => self.in_paste_mode = true,
836 [2026] => {},
839
840 _ => {
841 warn!(
842 "Unhandled CSI h command: CSI {:?} {:?} h",
843 intermediates,
844 params.iter().collect::<Vec<&[u16]>>()
845 );
846 return;
847 }
848 }
849 }
850 _ => warn!(
851 "Unhandled CSI h command: CSI {:?} {:?} h",
852 intermediates,
853 params.iter().collect::<Vec<&[u16]>>()
854 ),
855 }
856 'l' => match intermediates {
857 [b'?'] => while let Some(code) = params_iter.next() {
858 match code {
859 [1] => self.application_keypad_mode_enabled = false,
860 [6] => self.screen_mut().set_origin_mode(OriginMode::Term),
861 [25] => self.cursor_hidden = true,
862 [1049] => self.screen_mode = ScreenMode::Scrollback,
863 [2004] => self.in_paste_mode = false,
864 [2026] => {},
867 _ => {
868 warn!(
869 "Unhandled CSI l command: CSI {:?} {:?} l",
870 intermediates,
871 params.iter().collect::<Vec<&[u16]>>()
872 );
873 return;
874 }
875 }
876 }
877 _ => warn!(
878 "Unhandled CSI l command: CSI {:?} {:?} l",
879 intermediates,
880 params.iter().collect::<Vec<&[u16]>>()
881 ),
882 },
883 'n' => while let Some(param) = params_iter.next() {
885 match param {
886 [6] => debug!("ignoring DSR (CSI 6 n), that's the real terminal's job"),
895 _ => {}
896 }
897 },
898
899 'm' => while let Some(param) = params_iter.next() {
901 match param {
902 [] | [0] => self.cursor_attrs = term::Attrs::default(),
903
904 [4] => self.cursor_attrs.underline = Some(UnderlineStyle::Single),
915 [21] => self.cursor_attrs.underline = Some(UnderlineStyle::Double),
916 [24] => self.cursor_attrs.underline = None,
917
918 [1] => self.cursor_attrs.font_weight = Some(FontWeight::Bold),
920 [2] => self.cursor_attrs.font_weight = Some(FontWeight::Faint),
921 [22] => self.cursor_attrs.font_weight = None,
922
923 [3] => self.cursor_attrs.italic = true,
925 [23] => self.cursor_attrs.italic = false,
926
927 [7] => self.cursor_attrs.inverse = true,
929 [27] => self.cursor_attrs.inverse = false,
930
931 [5] => self.cursor_attrs.blink = Some(BlinkStyle::Slow),
933 [6] => self.cursor_attrs.blink = Some(BlinkStyle::Rapid),
934 [25] => self.cursor_attrs.blink = None,
935
936 [8] => self.cursor_attrs.conceal = true,
938 [28] => self.cursor_attrs.conceal = false,
939
940 [9] => self.cursor_attrs.strikethrough = true,
942 [29] => self.cursor_attrs.strikethrough = false,
943
944 [51] => self.cursor_attrs.framed = Some(FrameStyle::Frame),
946 [52] => self.cursor_attrs.framed = Some(FrameStyle::Circle),
947 [54] => self.cursor_attrs.framed = None,
948
949 [53] => self.cursor_attrs.overline = true,
951 [55] => self.cursor_attrs.overline = false,
952
953 [49] => self.cursor_attrs.bgcolor = term::Color::Default,
955 [n] if 40 <= *n && *n < 48 => match (*n - 40).try_into() {
956 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
957 Err(e) => warn!("out of bounds bgcolor idx (1): {e:?}"),
958 }
959 [n] if 100 <= *n && *n < 108 => match (*n - 92).try_into() {
960 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
961 Err(e) => warn!("out of bounds bgcolor idx (2): {e:?}"),
962 }
963 [48] => match params_iter.next() {
964 Some([5]) => {
965 let n = param_or(&mut params_iter, 0);
966 match n.try_into() {
967 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
968 Err(e) => warn!("out of bounds bgcolor idx (3): {e:?}"),
969 }
970 },
971 Some([2]) => {
972 let r = param_or(&mut params_iter, 0);
978 let g = param_or(&mut params_iter, 0);
979 let b = param_or(&mut params_iter, 0);
980 if let (Ok(r), Ok(g), Ok(b)) = (r.try_into(), g.try_into(), b.try_into()) {
981 self.cursor_attrs.bgcolor = term::Color::Rgb(r, g, b);
982 } else {
983 warn!("out of bounds color codes for CSI 48 2 ... m");
984 }
985 },
986 _ => warn!("unhandled incomplete 'CSI 48 ... m'"),
987 },
988
989 [39] => self.cursor_attrs.fgcolor = term::Color::Default,
991 [n] if 30 <= *n && *n < 38 => match (*n - 30).try_into() {
992 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
993 Err(e) => warn!("out of bounds fgcolor idx (1): {e:?}"),
994 }
995 [n] if 90 <= *n && *n < 98 => match (*n - 82).try_into() {
996 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
997 Err(e) => warn!("out of bounds fgcolor idx (2): {e:?}"),
998 }
999 [38] => match params_iter.next() {
1000 Some([5]) => {
1001
1002 let n = param_or(&mut params_iter, 0);
1003 match n.try_into() {
1004 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
1005 Err(e) => warn!("out of bounds fgcolor idx (3): {e:?}"),
1006 }
1007 },
1008 Some([2]) => {
1009 let r = param_or(&mut params_iter, 0);
1015 let g = param_or(&mut params_iter, 0);
1016 let b = param_or(&mut params_iter, 0);
1017 if let (Ok(r), Ok(g), Ok(b)) = (r.try_into(), g.try_into(), b.try_into()) {
1018 self.cursor_attrs.fgcolor = term::Color::Rgb(r, g, b);
1019 } else {
1020 warn!("out of bounds color codes for CSI 38 2 ... m");
1021 }
1022 },
1023 _ => warn!("unhandled incomplete 'CSI 38 ... m'"),
1024 },
1025
1026 _ => warn!("unhandled 'CSI {param:?} m'"),
1027 }
1028 }
1029 'p' => match intermediates {
1030 [b'!'] => {
1032 self.tabstops.fill(false);
1033 let width = self.screen().size.width;
1034 self.fill_tabstops(0, width);
1035
1036 warn!("DECSTR only partially handled");
1037 }
1038 _ => warn!(
1039 "Unhandled CSI p command: CSI {:?} {:?} p",
1040 intermediates,
1041 params.iter().collect::<Vec<&[u16]>>()
1042 ),
1043 },
1044 'r' => {
1046 let top = maybe_param(&mut params_iter);
1047 let bottom = maybe_param(&mut params_iter);
1048
1049 let screen = self.screen_mut();
1050 screen.set_scroll_region(match (top, bottom) {
1051 (None, None) => term::ScrollRegion::TrackSize,
1052 (Some(t), None) => term::ScrollRegion::Window {
1053 top: t.saturating_sub(1) as usize,
1054 bottom: screen.size.height,
1055 },
1056 (None, Some(b)) => term::ScrollRegion::Window {
1057 top: 0,
1058 bottom: b as usize,
1059 },
1060 (Some(t), Some(b)) => term::ScrollRegion::Window {
1061 top: t.saturating_sub(1) as usize,
1062 bottom: b as usize,
1063 }
1064 });
1065 }
1066
1067 _ => {
1068 warn!("unhandled action {}", action);
1069 }
1070 }
1071 }
1072
1073 fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
1074 if ignore {
1075 warn!("malformed ESC seq");
1076 return;
1077 }
1078 trace!("esc_dispatch: {}", byte);
1079
1080 match (intermediates, byte) {
1081 ([], b'7') => {
1083 let attrs = self.cursor_attrs.clone();
1084 let screen = self.screen_mut();
1085 let pos = screen.cursor.clone();
1086 screen.saved_cursor = SavedCursor { pos, attrs };
1087 }
1088 ([], b'8') => {
1090 let screen = self.screen_mut();
1091 screen.cursor = screen.saved_cursor.pos;
1092 self.cursor_attrs = screen.saved_cursor.attrs.clone();
1093 }
1094 ([], b'H') => {
1096 let col = self.screen().cursor.col;
1097 self.tabstops.set(col, true);
1098 }
1099 ([], b'c') => {
1101 self.tabstops.fill(false);
1102 let width = self.screen().size.width;
1103 self.fill_tabstops(0, width);
1104
1105 warn!("RIS only partially handled");
1106 }
1107
1108 ([], b'=') => self.application_keypad_mode_enabled = true,
1109 ([], b'>') => self.application_keypad_mode_enabled = false,
1110
1111 ([], 92) => {}
1114
1115 _ => warn!("unhandled ESC seq ({intermediates:?}, {byte})"),
1116 }
1117 }
1118
1119 fn terminated(&self) -> bool {
1120 false
1121 }
1122}
1123
1124fn param_or<'params>(params: &mut vte::ParamsIter<'params>, default: u16) -> u16 {
1125 maybe_param(params).unwrap_or(default)
1126}
1127
1128fn maybe_param<'params>(params: &mut vte::ParamsIter<'params>) -> Option<u16> {
1129 match params.next() {
1130 Some([0]) => None,
1131 Some([p]) => Some(*p),
1132 _ => None,
1133 }
1134}
1135
1136const NONE_VEC: Option<Vec<u8>> = None;