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 UnderlineStyle,
23 },
24};
25
26use smallvec::SmallVec;
27use tracing::{debug, warn, trace};
28
29mod altscreen;
30mod cell;
31mod line;
32mod screen;
33mod scrollback;
34
35#[cfg(not(feature = "internal-test"))]
36mod term;
37
38#[cfg(feature = "internal-test")]
39pub mod term;
40
41pub struct Term {
43 parser: vte::Parser,
44 state: State,
45}
46
47impl Term {
48 pub fn new(scrollback_lines: usize, size: Size) -> Self {
57 Term { parser: vte::Parser::new(), state: State::new(scrollback_lines, size) }
58 }
59
60 pub fn size(&self) -> Size {
62 self.state.screen().size
63 }
64
65 pub fn resize(&mut self, size: Size) {
70 if size.height > self.scrollback_lines() {
71 self.set_scrollback_lines(size.height);
72 }
73
74 self.state.scrollback.resize(size);
75 self.state.altscreen.resize(size);
76 }
77
78 pub fn scrollback_lines(&self) -> usize {
80 self.state.scrollback.scrollback_lines().expect("scrollback screen to have lines")
81 }
82
83 pub fn set_scrollback_lines(&mut self, scrollback_lines: usize) {
91 self.state.scrollback.set_scrollback_lines(scrollback_lines);
92 }
93
94 pub fn process(&mut self, buf: &[u8]) {
97 self.parser.advance(&mut self.state, buf);
98 }
99
100 pub fn contents(&self, dump_region: ContentRegion) -> Vec<u8> {
105 let mut buf = vec![];
106 term::control_codes().clear_attrs.term_input_into(&mut buf);
107 term::ControlCodes::cursor_position(1, 1).term_input_into(&mut buf);
108 term::control_codes().clear_screen.term_input_into(&mut buf);
109 self.state.dump_contents_into(&mut buf, dump_region);
110
111 buf
112 }
113}
114
115#[derive(Debug, Eq, PartialEq, Clone)]
117pub enum ContentRegion {
118 All,
120 Screen,
122 BottomLines(usize),
124}
125
126impl std::fmt::Display for Term {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 self.state.fmt(f)
129 }
130}
131
132#[derive(Debug, Clone, Copy, Eq, PartialEq)]
134pub struct Size {
135 pub width: usize,
136 pub height: usize,
137}
138
139struct State {
141 scrollback: Screen,
143 altscreen: Screen,
145 screen_mode: ScreenMode,
147 cursor_attrs: term::Attrs,
151 title: Option<SmallVec<[u8; 8]>>,
153 icon_name: Option<SmallVec<[u8; 8]>>,
155 working_dir: Option<WorkingDir>,
158 palette_overrides: BTreeMap<usize, Vec<u8>>,
162 functional_colors: [Option<Vec<u8>>; 10],
165 cursor_hidden: bool,
168 application_keypad_mode_enabled: bool,
171 in_paste_mode: bool,
173}
174
175struct WorkingDir {
176 host: SmallVec<[u8; 8]>,
177 dir: SmallVec<[u8; 8]>,
178}
179
180impl std::fmt::Display for State {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 match self.screen_mode {
183 ScreenMode::Scrollback => {
184 writeln!(f, "Screen Mode: Scrollback")?;
185 write!(f, "{}", self.scrollback)?;
186 }
187 ScreenMode::Alt => {
188 writeln!(f, "Screen Mode: AltScreen")?;
189 write!(f, "{}", self.altscreen)?;
190 }
191 }
192
193 Ok(())
194 }
195}
196
197impl State {
198 fn new(scrollback_lines: usize, size: Size) -> Self {
199 State {
200 scrollback: Screen::scrollback(scrollback_lines, size),
201 altscreen: Screen::alt(size),
202 screen_mode: ScreenMode::Scrollback,
203 cursor_attrs: term::Attrs::default(),
204 title: None,
205 icon_name: None,
206 working_dir: None,
207 palette_overrides: BTreeMap::new(),
208 functional_colors: [NONE_VEC; 10],
209 cursor_hidden: false,
210 application_keypad_mode_enabled: false,
211 in_paste_mode: false,
212 }
213 }
214
215 fn screen_mut(&mut self) -> &mut Screen {
216 match self.screen_mode {
217 ScreenMode::Scrollback => &mut self.scrollback,
218 ScreenMode::Alt => &mut self.altscreen,
219 }
220 }
221
222 fn screen(&self) -> &Screen {
223 match self.screen_mode {
224 ScreenMode::Scrollback => &self.scrollback,
225 ScreenMode::Alt => &self.altscreen,
226 }
227 }
228
229 fn dump_contents_into(&self, buf: &mut Vec<u8>, dump_region: ContentRegion) {
230 match self.screen_mode {
231 ScreenMode::Scrollback => self.scrollback.dump_contents_into(buf, dump_region),
232 ScreenMode::Alt => self.altscreen.dump_contents_into(buf, dump_region),
233 }
234
235 let controls = term::control_codes();
236
237 controls.clear_attrs.term_input_into(buf);
240 let codes = term::Attrs::default().transition_to(&self.cursor_attrs);
241 for c in codes.into_iter() {
242 c.term_input_into(buf);
243 }
244
245 match (&self.title, &self.icon_name) {
250 (Some(title), Some(icon_name)) if title == icon_name => {
251 ControlCodes::set_title_and_icon_name(title.clone()).term_input_into(buf)
252 }
253 (Some(title), Some(icon_name)) => {
254 ControlCodes::set_title(title.clone()).term_input_into(buf);
255 ControlCodes::set_icon_name(icon_name.clone()).term_input_into(buf);
256 }
257 (Some(title), None) => {
258 ControlCodes::set_title(title.clone()).term_input_into(buf);
259 }
260 (None, Some(icon_name)) => {
261 ControlCodes::set_icon_name(icon_name.clone()).term_input_into(buf);
262 }
263 (None, None) => {}
264 }
265
266 if let Some(working_dir) = &self.working_dir {
267 ControlCodes::set_working_dir(working_dir.host.clone(), working_dir.dir.clone())
268 .term_input_into(buf);
269 }
270
271 if !self.palette_overrides.is_empty() {
272 ControlCodes::set_color_indices(
273 self.palette_overrides
274 .iter()
275 .map(|(idx, color_spec)| (*idx, SmallVec::from(color_spec.as_slice()))),
276 )
277 .term_input_into(buf);
278 }
279
280 if self.cursor_hidden {
281 controls.hide_cursor.term_input_into(buf);
282 }
283 if self.application_keypad_mode_enabled {
284 controls.enable_application_keypad_mode.term_input_into(buf);
285 }
286 if self.in_paste_mode {
287 controls.enable_paste_mode.term_input_into(buf);
288 }
289
290 let mut functional_color_idx = 0;
293 while functional_color_idx < self.functional_colors.len() {
294 if let Some(color_spec) = &self.functional_colors[functional_color_idx] {
295 let start_idx = functional_color_idx;
296 let mut color_specs = vec![color_spec.as_slice()];
297
298 functional_color_idx += 1;
299 while functional_color_idx < self.functional_colors.len() {
300 if let Some(s) = &self.functional_colors[functional_color_idx] {
301 color_specs.push(s.as_slice());
302 } else {
303 break;
304 }
305 functional_color_idx += 1;
306 }
307
308 ControlCodes::set_functional_color(start_idx, color_specs).term_input_into(buf);
309 }
310
311 functional_color_idx += 1;
312 }
313 }
314
315 fn set_functional_color<'a, I>(&mut self, mut idx: usize, mut params_iter: I)
318 where
319 I: Iterator<Item = &'a &'a [u8]>,
320 {
321 while let Some(color_spec) = params_iter.next() {
322 if idx >= self.functional_colors.len() {
323 return;
324 }
325
326 if *color_spec != [b'?'] {
327 self.functional_colors[idx] = Some(Vec::from(*color_spec));
328 }
329
330 idx += 1;
331 }
332 }
333}
334
335enum ScreenMode {
337 Scrollback,
338 Alt,
339}
340
341impl vte::Perform for State {
342 fn print(&mut self, c: char) {
343 let attrs = self.cursor_attrs.clone();
344 let screen = self.screen_mut();
345 screen.snap_to_bottom();
346 if let Err(e) = screen.write_at_cursor(Cell::new(c, attrs)) {
347 warn!("writing char at cursor: {e:?}");
348 }
349 }
350
351 fn execute(&mut self, byte: u8) {
352 match byte {
353 b'\n' => self.screen_mut().cursor.row += 1,
354 b'\r' => self.screen_mut().cursor.col = 0,
355 _ => {
356 warn!("execute: unhandled byte {}", byte);
357 }
358 }
359 }
360
361 fn hook(&mut self, _params: &vte::Params, intermediates: &[u8], ignore: bool, action: char) {
362 debug!("unhandled hook{}: {intermediates:?} {action}",
363 if ignore { " (ignored)" } else { "" });
364 }
365
366 fn put(&mut self, byte: u8) {
367 trace!("unhandled put: {byte}");
368 }
369
370 fn unhook(&mut self) {
371 debug!("unhandled unhook");
372 }
373
374 #[rustfmt::skip]
383 fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
384 let mut params_iter = params.iter();
385 match params_iter.next() {
386 Some([b'0']) => if let Some(title) = params_iter.next() {
388 self.title = Some(title.to_vec().into());
389 self.icon_name = Some(title.to_vec().into());
390 } else {
391 warn!("OSC 0 with no title param");
392 },
393 Some([b'1']) => if let Some(icon_name) = params_iter.next() {
394 self.icon_name = Some(icon_name.to_vec().into());
395 } else {
396 warn!("OSC 1 with no icon_name param");
397 },
398 Some([b'2']) => if let Some(title) = params_iter.next() {
399 self.title = Some(title.to_vec().into());
400 } else {
401 warn!("OSC 2 with no title param");
402 },
403
404 Some([b'4']) => while let (Some(idx), Some(color_spec)) = (params_iter.next(), params_iter.next()) {
406 if *color_spec == [b'?'] {
407 continue;
411 }
412
413 match std::str::from_utf8(idx) {
414 Ok(s) => match s.parse::<usize>() {
415 Ok(i) => {
416 self.palette_overrides.insert(i, color_spec.to_vec());
417 },
418 Err(e) => warn!("OSC 4: idx is an invalid number '{s}': {e}"),
419 },
420 Err(e) => warn!("OSC 4: invalid idx '{idx:?}': {e}"),
421 }
422 },
423 Some([b'1', b'0', b'4']) => while let Some(idx) = params_iter.next() {
424 match std::str::from_utf8(idx) {
425 Ok(s) => match s.parse::<usize>() {
426 Ok(i) => {
427 self.palette_overrides.remove(&i);
428 },
429 Err(e) => warn!("OSC 104: idx is an invalid number '{s}': {e}"),
430 },
431 Err(e) => warn!("OSC 104: invalid idx '{idx:?}': {e}"),
432 }
433 },
434
435 Some([b'7']) => if let (Some(host), Some(dir)) = (params_iter.next(), params_iter.next()) {
437 self.working_dir = Some(WorkingDir {
438 host: host.to_vec().into(),
439 dir: dir.to_vec().into(),
440 });
441 } else {
442 warn!("OSC 7 with fewer than 2 params");
443 },
444
445 Some([b'8']) => if let (Some(params), Some(url)) = (params_iter.next(), params_iter.next()) {
447 if params.is_empty() && url.is_empty() {
448 self.cursor_attrs.link_target = None;
449 } else {
450 self.cursor_attrs.link_target = Some(LinkTarget {
451 params: SmallVec::from_slice(params),
452 url: SmallVec::from_slice(url),
453 });
454 }
455 } else {
456 self.cursor_attrs.link_target = None;
457 },
458
459 Some([b'1', x]) if b'0' <= *x && *x <= b'9' =>
461 self.set_functional_color((*x - b'0') as usize, params_iter),
462
463 Some([b'5', b'2']) => debug!("ignoring OSC 52 (clipboard)"),
464 Some([b'9']) => debug!("ignoring OSC 9 (desktop notification)"),
465 Some([b'7', b'7', b'7']) => debug!("ignoring OSC 777"),
466 Some([b'1', b'3', b'3']) => debug!("ignoring OSC 133 (iterm2 marks)"),
467
468 _ => warn!("unhandled 'OSC {:?} {}'", params, if bell_terminated {
469 "BEL"
470 } else {
471 "ST"
472 }),
473 }
474 }
475
476 #[rustfmt::skip]
483 fn csi_dispatch(
484 &mut self,
485 params: &vte::Params,
486 intermediates: &[u8],
487 ignore: bool,
488 action: char,
489 ) {
490 if ignore {
491 warn!("malformed CSI seq");
492 return;
493 }
494
495 let mut params_iter = params.iter();
496
497 match action {
498 'A' => {
500 let n = param_or(&mut params_iter, 1) as usize;
501 let screen = self.screen_mut();
502 screen.cursor.row = screen.cursor.row.saturating_sub(n);
503 screen.clamp();
504 }
505 'B' => {
507 let n = param_or(&mut params_iter, 1) as usize;
508 let screen = self.screen_mut();
509 screen.cursor.row += n;
510 screen.clamp();
511 }
512 'C' => {
514 let n = param_or(&mut params_iter, 1) as usize;
515 let screen = self.screen_mut();
516 screen.cursor.col += n;
517 screen.clamp();
518 }
519 'D' => {
521 let n = param_or(&mut params_iter, 1) as usize;
522 let screen = self.screen_mut();
523 screen.cursor.col = screen.cursor.col.saturating_sub(n);
524 screen.clamp();
525 }
526 'E' => {
528 let n = param_or(&mut params_iter, 1) as usize;
529 let screen = self.screen_mut();
530 screen.cursor.row += n;
531 screen.cursor.col = 0;
532 screen.clamp();
533 }
534 'F' => {
536 let n = param_or(&mut params_iter, 1) as usize;
537 let screen = self.screen_mut();
538 screen.cursor.row = screen.cursor.row.saturating_sub(n);
539 screen.cursor.col = 0;
540 screen.clamp();
541 }
542 'G' => {
544 let n = param_or(&mut params_iter, 1) as usize;
545 let n = n.saturating_sub(1); let screen = self.screen_mut();
548 screen.cursor.col = n;
549 screen.clamp();
550 }
551 'H' => {
553 let row = param_or(&mut params_iter, 1) as usize;
555 let col = param_or(&mut params_iter, 1) as usize;
556 let screen = self.screen_mut();
557 screen.set_cursor(term::Pos { row, col });
558 screen.clamp();
559 }
560 'J' => while let Some(code) = params_iter.next() {
562 match code {
563 [] | [0] => self.screen_mut().erase_to_end(),
564 [1] => self.screen_mut().erase_from_start(),
565 [2] => self.screen_mut().erase(false),
566 [3] => self.screen_mut().erase(true),
567 _ => warn!("unhandled 'CSI {code:?} J'"),
568 }
569 }
570 'K' => while let Some(code) = params_iter.next() {
572 match code {
573 [] | [0] => {
574 let screen = self.screen_mut();
575 let col = screen.cursor.col;
576 if let Some(l) = screen.get_line_mut() {
577 l.erase(line::Section::ToEnd(col));
578 }
579 }
580 [1] => {
581 let screen = self.screen_mut();
582 let col = screen.cursor.col;
583 if let Some(l) = screen.get_line_mut() {
584 l.erase(line::Section::StartTo(col));
585 }
586 }
587 [2] => if let Some(l) = self.screen_mut().get_line_mut() {
588 l.erase(line::Section::Whole);
589 }
590 _ => warn!("unhandled 'CSI {code:?} K'"),
591 }
592 }
593 'L' => {
595 let n = param_or(&mut params_iter, 1) as usize;
596 self.screen_mut().insert_lines(n);
597 }
598 'M' => {
600 let n = param_or(&mut params_iter, 1) as usize;
601 self.screen_mut().delete_lines(n);
602 }
603 'S' => {
605 let n = param_or(&mut params_iter, 1) as usize;
606 self.screen_mut().scroll_up(n as usize);
607 }
608 'T' => {
610 let n = param_or(&mut params_iter, 1) as usize;
611 self.screen_mut().scroll_down(n as usize);
612 }
613
614 '@' => {
616 let n = param_or(&mut params_iter, 1) as usize;
617
618 let screen = self.screen_mut();
619 let width = screen.size.width;
620 let col = screen.cursor.col;
621 if let Some(l) = screen.get_line_mut() {
622 l.insert_character(width, col, n);
623 }
624 }
625 'P' => {
627 let n = param_or(&mut params_iter, 1) as usize;
628
629 let attrs = self.cursor_attrs.clone();
630
631 let screen = self.screen_mut();
632 let width = screen.size.width;
633 let col = screen.cursor.col;
634 if let Some(l) = screen.get_line_mut() {
635 l.delete_character(width, col, &attrs, n);
636 }
637 }
638
639 's' => {
641 let screen = self.screen_mut();
642 let cursor = screen.cursor.clone();
643 screen.saved_cursor.pos = cursor;
644 }
645 'u' => {
647 let screen = self.screen_mut();
648 screen.cursor = screen.saved_cursor.pos;
649 screen.clamp();
650 }
651
652 'h' => match intermediates {
653 [b'?'] => while let Some(code) = params_iter.next() {
654 match code {
655 [1] => self.application_keypad_mode_enabled = true,
656 [6] => self.screen_mut().set_origin_mode(OriginMode::ScrollRegion),
657 [25] => self.cursor_hidden = false,
658 [1049] => {
660 self.altscreen = Screen::alt(self.altscreen.size);
663 self.screen_mode = ScreenMode::Alt;
664 }
665 [2004] => self.in_paste_mode = true,
666
667 _ => {
668 warn!(
669 "Unhandled CSI l command: CSI {:?} {:?} l",
670 intermediates,
671 params.iter().collect::<Vec<&[u16]>>()
672 );
673 return;
674 }
675 }
676 }
677 _ => warn!(
678 "Unhandled CSI h command: CSI {:?} {:?} h",
679 intermediates,
680 params.iter().collect::<Vec<&[u16]>>()
681 ),
682 }
683 'l' => match intermediates {
684 [b'?'] => while let Some(code) = params_iter.next() {
685 match code {
686 [1] => self.application_keypad_mode_enabled = false,
687 [6] => self.screen_mut().set_origin_mode(OriginMode::Term),
688 [25] => self.cursor_hidden = true,
689 [1049] => self.screen_mode = ScreenMode::Scrollback,
690 [2004] => self.in_paste_mode = false,
691 _ => {
692 warn!(
693 "Unhandled CSI l command: CSI {:?} {:?} l",
694 intermediates,
695 params.iter().collect::<Vec<&[u16]>>()
696 );
697 return;
698 }
699 }
700 }
701 _ => warn!(
702 "Unhandled CSI l command: CSI {:?} {:?} l",
703 intermediates,
704 params.iter().collect::<Vec<&[u16]>>()
705 ),
706 },
707 'n' => while let Some(param) = params_iter.next() {
709 match param {
710 [6] => debug!("ignoring DSR (CSI 6 n), that's the real terminal's job"),
719 _ => {}
720 }
721 },
722
723 'm' => while let Some(param) = params_iter.next() {
725 match param {
726 [] | [0] => self.cursor_attrs = term::Attrs::default(),
727
728 [4] => self.cursor_attrs.underline = Some(UnderlineStyle::Single),
739 [21] => self.cursor_attrs.underline = Some(UnderlineStyle::Double),
740 [24] => self.cursor_attrs.underline = None,
741
742 [1] => self.cursor_attrs.font_weight = Some(FontWeight::Bold),
744 [2] => self.cursor_attrs.font_weight = Some(FontWeight::Faint),
745 [22] => self.cursor_attrs.font_weight = None,
746
747 [3] => self.cursor_attrs.italic = true,
749 [23] => self.cursor_attrs.italic = false,
750
751 [7] => self.cursor_attrs.inverse = true,
753 [27] => self.cursor_attrs.inverse = false,
754
755 [5] => self.cursor_attrs.blink = Some(BlinkStyle::Slow),
757 [6] => self.cursor_attrs.blink = Some(BlinkStyle::Rapid),
758 [25] => self.cursor_attrs.blink = None,
759
760 [8] => self.cursor_attrs.conceal = true,
762 [28] => self.cursor_attrs.conceal = false,
763
764 [9] => self.cursor_attrs.strikethrough = true,
766 [29] => self.cursor_attrs.strikethrough = false,
767
768 [51] => self.cursor_attrs.framed = Some(FrameStyle::Frame),
770 [52] => self.cursor_attrs.framed = Some(FrameStyle::Circle),
771 [54] => self.cursor_attrs.framed = None,
772
773 [53] => self.cursor_attrs.overline = true,
775 [55] => self.cursor_attrs.overline = false,
776
777 [49] => self.cursor_attrs.bgcolor = term::Color::Default,
779 [n] if 40 <= *n && *n < 48 => match (*n - 40).try_into() {
780 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
781 Err(e) => warn!("out of bounds bgcolor idx (1): {e:?}"),
782 }
783 [n] if 100 <= *n && *n < 108 => match (*n - 92).try_into() {
784 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
785 Err(e) => warn!("out of bounds bgcolor idx (2): {e:?}"),
786 }
787 [48] => match params_iter.next() {
788 Some([5]) => {
789 let n = param_or(&mut params_iter, 0);
790 match n.try_into() {
791 Ok(i) => self.cursor_attrs.bgcolor = term::Color::Idx(i),
792 Err(e) => warn!("out of bounds bgcolor idx (3): {e:?}"),
793 }
794 },
795 Some([2]) => {
796 let r = param_or(&mut params_iter, 0);
802 let g = param_or(&mut params_iter, 0);
803 let b = param_or(&mut params_iter, 0);
804 if let (Ok(r), Ok(g), Ok(b)) = (r.try_into(), g.try_into(), b.try_into()) {
805 self.cursor_attrs.bgcolor = term::Color::Rgb(r, g, b);
806 } else {
807 warn!("out of bounds color codes for CSI 48 2 ... m");
808 }
809 },
810 _ => warn!("unhandled incomplete 'CSI 48 ... m'"),
811 },
812
813 [39] => self.cursor_attrs.fgcolor = term::Color::Default,
815 [n] if 30 <= *n && *n < 38 => match (*n - 30).try_into() {
816 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
817 Err(e) => warn!("out of bounds fgcolor idx (1): {e:?}"),
818 }
819 [n] if 90 <= *n && *n < 98 => match (*n - 82).try_into() {
820 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
821 Err(e) => warn!("out of bounds fgcolor idx (2): {e:?}"),
822 }
823 [38] => match params_iter.next() {
824 Some([5]) => {
825
826 let n = param_or(&mut params_iter, 0);
827 match n.try_into() {
828 Ok(i) => self.cursor_attrs.fgcolor = term::Color::Idx(i),
829 Err(e) => warn!("out of bounds fgcolor idx (3): {e:?}"),
830 }
831 },
832 Some([2]) => {
833 let r = param_or(&mut params_iter, 0);
839 let g = param_or(&mut params_iter, 0);
840 let b = param_or(&mut params_iter, 0);
841 if let (Ok(r), Ok(g), Ok(b)) = (r.try_into(), g.try_into(), b.try_into()) {
842 self.cursor_attrs.fgcolor = term::Color::Rgb(r, g, b);
843 } else {
844 warn!("out of bounds color codes for CSI 38 2 ... m");
845 }
846 },
847 _ => warn!("unhandled incomplete 'CSI 38 ... m'"),
848 },
849
850 _ => warn!("unhandled 'CSI {param:?} m'"),
851 }
852 }
853 'r' => {
855 let top = maybe_param(&mut params_iter);
856 let bottom = maybe_param(&mut params_iter);
857
858 let screen = self.screen_mut();
859 screen.set_scroll_region(match (top, bottom) {
860 (None, None) => term::ScrollRegion::TrackSize,
861 (Some(t), None) => term::ScrollRegion::Window {
862 top: t.saturating_sub(1) as usize,
863 bottom: screen.size.height,
864 },
865 (None, Some(b)) => term::ScrollRegion::Window {
866 top: 0,
867 bottom: b as usize,
868 },
869 (Some(t), Some(b)) => term::ScrollRegion::Window {
870 top: t.saturating_sub(1) as usize,
871 bottom: b as usize,
872 }
873 });
874 }
875
876 _ => {
877 warn!("unhandled action {}", action);
878 }
879 }
880 }
881
882 fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
883 if ignore {
884 warn!("malformed ESC seq");
885 return;
886 }
887
888 match (intermediates, byte) {
889 ([], b'7') => {
891 let attrs = self.cursor_attrs.clone();
892 let screen = self.screen_mut();
893 let pos = screen.cursor.clone();
894 screen.saved_cursor = SavedCursor { pos, attrs };
895 }
896 ([], b'8') => {
898 let screen = self.screen_mut();
899 screen.cursor = screen.saved_cursor.pos;
900 self.cursor_attrs = screen.saved_cursor.attrs.clone();
901 }
902
903 _ => warn!("unhandled ESC seq ({intermediates:?}, {byte})"),
904 }
905 }
906
907 fn terminated(&self) -> bool {
908 false
909 }
910}
911
912fn param_or<'params>(params: &mut vte::ParamsIter<'params>, default: u16) -> u16 {
913 maybe_param(params).unwrap_or(default)
914}
915
916fn maybe_param<'params>(params: &mut vte::ParamsIter<'params>) -> Option<u16> {
917 match params.next() {
918 Some([0]) => None,
919 Some([p]) => Some(*p),
920 _ => None,
921 }
922}
923
924const NONE_VEC: Option<Vec<u8>> = None;