vt100/
screen.rs

1use crate::term::BufWrite as _;
2use unicode_width::UnicodeWidthChar as _;
3
4const MODE_APPLICATION_KEYPAD: u8 = 0b0000_0001;
5const MODE_APPLICATION_CURSOR: u8 = 0b0000_0010;
6const MODE_HIDE_CURSOR: u8 = 0b0000_0100;
7const MODE_ALTERNATE_SCREEN: u8 = 0b0000_1000;
8const MODE_BRACKETED_PASTE: u8 = 0b0001_0000;
9
10/// The xterm mouse handling mode currently in use.
11#[derive(Copy, Clone, Debug, Eq, PartialEq)]
12pub enum MouseProtocolMode {
13    /// Mouse handling is disabled.
14    None,
15
16    /// Mouse button events should be reported on button press. Also known as
17    /// X10 mouse mode.
18    Press,
19
20    /// Mouse button events should be reported on button press and release.
21    /// Also known as VT200 mouse mode.
22    PressRelease,
23
24    // Highlight,
25    /// Mouse button events should be reported on button press and release, as
26    /// well as when the mouse moves between cells while a button is held
27    /// down.
28    ButtonMotion,
29
30    /// Mouse button events should be reported on button press and release,
31    /// and mouse motion events should be reported when the mouse moves
32    /// between cells regardless of whether a button is held down or not.
33    AnyMotion,
34    // DecLocator,
35}
36
37impl Default for MouseProtocolMode {
38    fn default() -> Self {
39        Self::None
40    }
41}
42
43/// The encoding to use for the enabled `MouseProtocolMode`.
44#[derive(Copy, Clone, Debug, Eq, PartialEq)]
45pub enum MouseProtocolEncoding {
46    /// Default single-printable-byte encoding.
47    Default,
48
49    /// UTF-8-based encoding.
50    Utf8,
51
52    /// SGR-like encoding.
53    Sgr,
54    // Urxvt,
55}
56
57impl Default for MouseProtocolEncoding {
58    fn default() -> Self {
59        Self::Default
60    }
61}
62
63/// Represents the overall terminal state.
64#[derive(Clone, Debug)]
65pub struct Screen {
66    grid: crate::grid::Grid,
67    alternate_grid: crate::grid::Grid,
68
69    attrs: crate::attrs::Attrs,
70    saved_attrs: crate::attrs::Attrs,
71
72    title: String,
73    icon_name: String,
74
75    modes: u8,
76    mouse_protocol_mode: MouseProtocolMode,
77    mouse_protocol_encoding: MouseProtocolEncoding,
78
79    audible_bell_count: usize,
80    visual_bell_count: usize,
81
82    errors: usize,
83}
84
85impl Screen {
86    pub(crate) fn new(
87        size: crate::grid::Size,
88        scrollback_len: usize,
89    ) -> Self {
90        let mut grid = crate::grid::Grid::new(size, scrollback_len);
91        grid.allocate_rows();
92        Self {
93            grid,
94            alternate_grid: crate::grid::Grid::new(size, 0),
95
96            attrs: crate::attrs::Attrs::default(),
97            saved_attrs: crate::attrs::Attrs::default(),
98
99            title: String::default(),
100            icon_name: String::default(),
101
102            modes: 0,
103            mouse_protocol_mode: MouseProtocolMode::default(),
104            mouse_protocol_encoding: MouseProtocolEncoding::default(),
105
106            audible_bell_count: 0,
107            visual_bell_count: 0,
108
109            errors: 0,
110        }
111    }
112
113    pub(crate) fn set_size(&mut self, rows: u16, cols: u16) {
114        self.grid.set_size(crate::grid::Size { rows, cols });
115        self.alternate_grid
116            .set_size(crate::grid::Size { rows, cols });
117    }
118
119    /// Returns the current size of the terminal.
120    ///
121    /// The return value will be (rows, cols).
122    #[must_use]
123    pub fn size(&self) -> (u16, u16) {
124        let size = self.grid().size();
125        (size.rows, size.cols)
126    }
127
128    /// Returns the current position in the scrollback.
129    ///
130    /// This position indicates the offset from the top of the screen, and is
131    /// `0` when the normal screen is in view.
132    #[must_use]
133    pub fn scrollback(&self) -> usize {
134        self.grid().scrollback()
135    }
136
137    pub(crate) fn set_scrollback(&mut self, rows: usize) {
138        self.grid_mut().set_scrollback(rows);
139    }
140
141    /// Returns the text contents of the terminal.
142    ///
143    /// This will not include any formatting information, and will be in plain
144    /// text format.
145    #[must_use]
146    pub fn contents(&self) -> String {
147        let mut contents = String::new();
148        self.write_contents(&mut contents);
149        contents
150    }
151
152    fn write_contents(&self, contents: &mut String) {
153        self.grid().write_contents(contents);
154    }
155
156    /// Returns the text contents of the terminal by row, restricted to the
157    /// given subset of columns.
158    ///
159    /// This will not include any formatting information, and will be in plain
160    /// text format.
161    ///
162    /// Newlines will not be included.
163    pub fn rows(
164        &self,
165        start: u16,
166        width: u16,
167    ) -> impl Iterator<Item = String> + '_ {
168        self.grid().visible_rows().map(move |row| {
169            let mut contents = String::new();
170            row.write_contents(&mut contents, start, width, false);
171            contents
172        })
173    }
174
175    /// Returns the text contents of the terminal logically between two cells.
176    /// This will include the remainder of the starting row after `start_col`,
177    /// followed by the entire contents of the rows between `start_row` and
178    /// `end_row`, followed by the beginning of the `end_row` up until
179    /// `end_col`. This is useful for things like determining the contents of
180    /// a clipboard selection.
181    #[must_use]
182    pub fn contents_between(
183        &self,
184        start_row: u16,
185        start_col: u16,
186        end_row: u16,
187        end_col: u16,
188    ) -> String {
189        match start_row.cmp(&end_row) {
190            std::cmp::Ordering::Less => {
191                let (_, cols) = self.size();
192                let mut contents = String::new();
193                for (i, row) in self
194                    .grid()
195                    .visible_rows()
196                    .enumerate()
197                    .skip(usize::from(start_row))
198                    .take(usize::from(end_row) - usize::from(start_row) + 1)
199                {
200                    if i == usize::from(start_row) {
201                        row.write_contents(
202                            &mut contents,
203                            start_col,
204                            cols - start_col,
205                            false,
206                        );
207                        if !row.wrapped() {
208                            contents.push('\n');
209                        }
210                    } else if i == usize::from(end_row) {
211                        row.write_contents(&mut contents, 0, end_col, false);
212                    } else {
213                        row.write_contents(&mut contents, 0, cols, false);
214                        if !row.wrapped() {
215                            contents.push('\n');
216                        }
217                    }
218                }
219                contents
220            }
221            std::cmp::Ordering::Equal => {
222                if start_col < end_col {
223                    self.rows(start_col, end_col - start_col)
224                        .nth(usize::from(start_row))
225                        .unwrap_or_default()
226                } else {
227                    String::new()
228                }
229            }
230            std::cmp::Ordering::Greater => String::new(),
231        }
232    }
233
234    /// Return escape codes sufficient to reproduce the entire contents of the
235    /// current terminal state. This is a convenience wrapper around
236    /// `contents_formatted`, `input_mode_formatted`, and `title_formatted`.
237    #[must_use]
238    pub fn state_formatted(&self) -> Vec<u8> {
239        let mut contents = vec![];
240        self.write_contents_formatted(&mut contents);
241        self.write_input_mode_formatted(&mut contents);
242        self.write_title_formatted(&mut contents);
243        contents
244    }
245
246    /// Return escape codes sufficient to turn the terminal state of the
247    /// screen `prev` into the current terminal state. This is a convenience
248    /// wrapper around `contents_diff`, `input_mode_diff`, `title_diff`, and
249    /// `bells_diff`.
250    #[must_use]
251    pub fn state_diff(&self, prev: &Self) -> Vec<u8> {
252        let mut contents = vec![];
253        self.write_contents_diff(&mut contents, prev);
254        self.write_input_mode_diff(&mut contents, prev);
255        self.write_title_diff(&mut contents, prev);
256        self.write_bells_diff(&mut contents, prev);
257        contents
258    }
259
260    /// Returns the formatted visible contents of the terminal.
261    ///
262    /// Formatting information will be included inline as terminal escape
263    /// codes. The result will be suitable for feeding directly to a raw
264    /// terminal parser, and will result in the same visual output.
265    #[must_use]
266    pub fn contents_formatted(&self) -> Vec<u8> {
267        let mut contents = vec![];
268        self.write_contents_formatted(&mut contents);
269        contents
270    }
271
272    fn write_contents_formatted(&self, contents: &mut Vec<u8>) {
273        crate::term::HideCursor::new(self.hide_cursor()).write_buf(contents);
274        let prev_attrs = self.grid().write_contents_formatted(contents);
275        self.attrs.write_escape_code_diff(contents, &prev_attrs);
276    }
277
278    /// Returns the formatted visible contents of the terminal by row,
279    /// restricted to the given subset of columns.
280    ///
281    /// Formatting information will be included inline as terminal escape
282    /// codes. The result will be suitable for feeding directly to a raw
283    /// terminal parser, and will result in the same visual output.
284    ///
285    /// You are responsible for positioning the cursor before printing each
286    /// row, and the final cursor position after displaying each row is
287    /// unspecified.
288    // the unwraps in this method shouldn't be reachable
289    #[allow(clippy::missing_panics_doc)]
290    pub fn rows_formatted(
291        &self,
292        start: u16,
293        width: u16,
294    ) -> impl Iterator<Item = Vec<u8>> + '_ {
295        let mut wrapping = false;
296        self.grid().visible_rows().enumerate().map(move |(i, row)| {
297            // number of rows in a grid is stored in a u16 (see Size), so
298            // visible_rows can never return enough rows to overflow here
299            let i = i.try_into().unwrap();
300            let mut contents = vec![];
301            row.write_contents_formatted(
302                &mut contents,
303                start,
304                width,
305                i,
306                wrapping,
307                None,
308                None,
309            );
310            if start == 0 && width == self.grid.size().cols {
311                wrapping = row.wrapped();
312            }
313            contents
314        })
315    }
316
317    /// Returns a terminal byte stream sufficient to turn the visible contents
318    /// of the screen described by `prev` into the visible contents of the
319    /// screen described by `self`.
320    ///
321    /// The result of rendering `prev.contents_formatted()` followed by
322    /// `self.contents_diff(prev)` should be equivalent to the result of
323    /// rendering `self.contents_formatted()`. This is primarily useful when
324    /// you already have a terminal parser whose state is described by `prev`,
325    /// since the diff will likely require less memory and cause less
326    /// flickering than redrawing the entire screen contents.
327    #[must_use]
328    pub fn contents_diff(&self, prev: &Self) -> Vec<u8> {
329        let mut contents = vec![];
330        self.write_contents_diff(&mut contents, prev);
331        contents
332    }
333
334    fn write_contents_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
335        if self.hide_cursor() != prev.hide_cursor() {
336            crate::term::HideCursor::new(self.hide_cursor())
337                .write_buf(contents);
338        }
339        let prev_attrs = self.grid().write_contents_diff(
340            contents,
341            prev.grid(),
342            prev.attrs,
343        );
344        self.attrs.write_escape_code_diff(contents, &prev_attrs);
345    }
346
347    /// Returns a sequence of terminal byte streams sufficient to turn the
348    /// visible contents of the subset of each row from `prev` (as described
349    /// by `start` and `width`) into the visible contents of the corresponding
350    /// row subset in `self`.
351    ///
352    /// You are responsible for positioning the cursor before printing each
353    /// row, and the final cursor position after displaying each row is
354    /// unspecified.
355    // the unwraps in this method shouldn't be reachable
356    #[allow(clippy::missing_panics_doc)]
357    pub fn rows_diff<'a>(
358        &'a self,
359        prev: &'a Self,
360        start: u16,
361        width: u16,
362    ) -> impl Iterator<Item = Vec<u8>> + 'a {
363        self.grid()
364            .visible_rows()
365            .zip(prev.grid().visible_rows())
366            .enumerate()
367            .map(move |(i, (row, prev_row))| {
368                // number of rows in a grid is stored in a u16 (see Size), so
369                // visible_rows can never return enough rows to overflow here
370                let i = i.try_into().unwrap();
371                let mut contents = vec![];
372                row.write_contents_diff(
373                    &mut contents,
374                    prev_row,
375                    start,
376                    width,
377                    i,
378                    false,
379                    false,
380                    crate::grid::Pos { row: i, col: start },
381                    crate::attrs::Attrs::default(),
382                );
383                contents
384            })
385    }
386
387    /// Returns terminal escape sequences sufficient to set the current
388    /// terminal's input modes.
389    ///
390    /// Supported modes are:
391    /// * application keypad
392    /// * application cursor
393    /// * bracketed paste
394    /// * xterm mouse support
395    #[must_use]
396    pub fn input_mode_formatted(&self) -> Vec<u8> {
397        let mut contents = vec![];
398        self.write_input_mode_formatted(&mut contents);
399        contents
400    }
401
402    fn write_input_mode_formatted(&self, contents: &mut Vec<u8>) {
403        crate::term::ApplicationKeypad::new(
404            self.mode(MODE_APPLICATION_KEYPAD),
405        )
406        .write_buf(contents);
407        crate::term::ApplicationCursor::new(
408            self.mode(MODE_APPLICATION_CURSOR),
409        )
410        .write_buf(contents);
411        crate::term::BracketedPaste::new(self.mode(MODE_BRACKETED_PASTE))
412            .write_buf(contents);
413        crate::term::MouseProtocolMode::new(
414            self.mouse_protocol_mode,
415            MouseProtocolMode::None,
416        )
417        .write_buf(contents);
418        crate::term::MouseProtocolEncoding::new(
419            self.mouse_protocol_encoding,
420            MouseProtocolEncoding::Default,
421        )
422        .write_buf(contents);
423    }
424
425    /// Returns terminal escape sequences sufficient to change the previous
426    /// terminal's input modes to the input modes enabled in the current
427    /// terminal.
428    #[must_use]
429    pub fn input_mode_diff(&self, prev: &Self) -> Vec<u8> {
430        let mut contents = vec![];
431        self.write_input_mode_diff(&mut contents, prev);
432        contents
433    }
434
435    fn write_input_mode_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
436        if self.mode(MODE_APPLICATION_KEYPAD)
437            != prev.mode(MODE_APPLICATION_KEYPAD)
438        {
439            crate::term::ApplicationKeypad::new(
440                self.mode(MODE_APPLICATION_KEYPAD),
441            )
442            .write_buf(contents);
443        }
444        if self.mode(MODE_APPLICATION_CURSOR)
445            != prev.mode(MODE_APPLICATION_CURSOR)
446        {
447            crate::term::ApplicationCursor::new(
448                self.mode(MODE_APPLICATION_CURSOR),
449            )
450            .write_buf(contents);
451        }
452        if self.mode(MODE_BRACKETED_PASTE) != prev.mode(MODE_BRACKETED_PASTE)
453        {
454            crate::term::BracketedPaste::new(self.mode(MODE_BRACKETED_PASTE))
455                .write_buf(contents);
456        }
457        crate::term::MouseProtocolMode::new(
458            self.mouse_protocol_mode,
459            prev.mouse_protocol_mode,
460        )
461        .write_buf(contents);
462        crate::term::MouseProtocolEncoding::new(
463            self.mouse_protocol_encoding,
464            prev.mouse_protocol_encoding,
465        )
466        .write_buf(contents);
467    }
468
469    /// Returns terminal escape sequences sufficient to set the current
470    /// terminal's window title.
471    #[must_use]
472    pub fn title_formatted(&self) -> Vec<u8> {
473        let mut contents = vec![];
474        self.write_title_formatted(&mut contents);
475        contents
476    }
477
478    fn write_title_formatted(&self, contents: &mut Vec<u8>) {
479        crate::term::ChangeTitle::new(&self.icon_name, &self.title, "", "")
480            .write_buf(contents);
481    }
482
483    /// Returns terminal escape sequences sufficient to change the previous
484    /// terminal's window title to the window title set in the current
485    /// terminal.
486    #[must_use]
487    pub fn title_diff(&self, prev: &Self) -> Vec<u8> {
488        let mut contents = vec![];
489        self.write_title_diff(&mut contents, prev);
490        contents
491    }
492
493    fn write_title_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
494        crate::term::ChangeTitle::new(
495            &self.icon_name,
496            &self.title,
497            &prev.icon_name,
498            &prev.title,
499        )
500        .write_buf(contents);
501    }
502
503    /// Returns terminal escape sequences sufficient to cause audible and
504    /// visual bells to occur if they have been received since the terminal
505    /// described by `prev`.
506    #[must_use]
507    pub fn bells_diff(&self, prev: &Self) -> Vec<u8> {
508        let mut contents = vec![];
509        self.write_bells_diff(&mut contents, prev);
510        contents
511    }
512
513    fn write_bells_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
514        if self.audible_bell_count != prev.audible_bell_count {
515            crate::term::AudibleBell::default().write_buf(contents);
516        }
517        if self.visual_bell_count != prev.visual_bell_count {
518            crate::term::VisualBell::default().write_buf(contents);
519        }
520    }
521
522    /// Returns terminal escape sequences sufficient to set the current
523    /// terminal's drawing attributes.
524    ///
525    /// Supported drawing attributes are:
526    /// * fgcolor
527    /// * bgcolor
528    /// * bold
529    /// * italic
530    /// * underline
531    /// * inverse
532    ///
533    /// This is not typically necessary, since `contents_formatted` will leave
534    /// the current active drawing attributes in the correct state, but this
535    /// can be useful in the case of drawing additional things on top of a
536    /// terminal output, since you will need to restore the terminal state
537    /// without the terminal contents necessarily being the same.
538    #[must_use]
539    pub fn attributes_formatted(&self) -> Vec<u8> {
540        let mut contents = vec![];
541        self.write_attributes_formatted(&mut contents);
542        contents
543    }
544
545    fn write_attributes_formatted(&self, contents: &mut Vec<u8>) {
546        crate::term::ClearAttrs::default().write_buf(contents);
547        self.attrs.write_escape_code_diff(
548            contents,
549            &crate::attrs::Attrs::default(),
550        );
551    }
552
553    /// Returns the current cursor position of the terminal.
554    ///
555    /// The return value will be (row, col).
556    #[must_use]
557    pub fn cursor_position(&self) -> (u16, u16) {
558        let pos = self.grid().pos();
559        (pos.row, pos.col)
560    }
561
562    /// Returns terminal escape sequences sufficient to set the current
563    /// cursor state of the terminal.
564    ///
565    /// This is not typically necessary, since `contents_formatted` will leave
566    /// the cursor in the correct state, but this can be useful in the case of
567    /// drawing additional things on top of a terminal output, since you will
568    /// need to restore the terminal state without the terminal contents
569    /// necessarily being the same.
570    ///
571    /// Note that the bytes returned by this function may alter the active
572    /// drawing attributes, because it may require redrawing existing cells in
573    /// order to position the cursor correctly (for instance, in the case
574    /// where the cursor is past the end of a row). Therefore, you should
575    /// ensure to reset the active drawing attributes if necessary after
576    /// processing this data, for instance by using `attributes_formatted`.
577    #[must_use]
578    pub fn cursor_state_formatted(&self) -> Vec<u8> {
579        let mut contents = vec![];
580        self.write_cursor_state_formatted(&mut contents);
581        contents
582    }
583
584    fn write_cursor_state_formatted(&self, contents: &mut Vec<u8>) {
585        crate::term::HideCursor::new(self.hide_cursor()).write_buf(contents);
586        self.grid()
587            .write_cursor_position_formatted(contents, None, None);
588
589        // we don't just call write_attributes_formatted here, because that
590        // would still be confusing - consider the case where the user sets
591        // their own unrelated drawing attributes (on a different parser
592        // instance) and then calls cursor_state_formatted. just documenting
593        // it and letting the user handle it on their own is more
594        // straightforward.
595    }
596
597    /// Returns the `Cell` object at the given location in the terminal, if it
598    /// exists.
599    #[must_use]
600    pub fn cell(&self, row: u16, col: u16) -> Option<&crate::cell::Cell> {
601        self.grid().visible_cell(crate::grid::Pos { row, col })
602    }
603
604    /// Returns whether the text in row `row` should wrap to the next line.
605    #[must_use]
606    pub fn row_wrapped(&self, row: u16) -> bool {
607        self.grid()
608            .visible_row(row)
609            .map_or(false, crate::row::Row::wrapped)
610    }
611
612    /// Returns the terminal's window title.
613    #[must_use]
614    pub fn title(&self) -> &str {
615        &self.title
616    }
617
618    /// Returns the terminal's icon name.
619    #[must_use]
620    pub fn icon_name(&self) -> &str {
621        &self.icon_name
622    }
623
624    /// Returns a value which changes every time an audible bell is received.
625    ///
626    /// Typically you would store this number after each call to `process`,
627    /// and trigger an audible bell whenever it changes.
628    ///
629    /// You shouldn't rely on the exact value returned here, since the exact
630    /// value will not be maintained by `contents_formatted` or
631    /// `contents_diff`.
632    #[must_use]
633    pub fn audible_bell_count(&self) -> usize {
634        self.audible_bell_count
635    }
636
637    /// Returns a value which changes every time an visual bell is received.
638    ///
639    /// Typically you would store this number after each call to `process`,
640    /// and trigger an visual bell whenever it changes.
641    ///
642    /// You shouldn't rely on the exact value returned here, since the exact
643    /// value will not be maintained by `contents_formatted` or
644    /// `contents_diff`.
645    #[must_use]
646    pub fn visual_bell_count(&self) -> usize {
647        self.visual_bell_count
648    }
649
650    /// Returns the number of parsing errors seen so far.
651    ///
652    /// Currently this only tracks invalid UTF-8 and control characters other
653    /// than `0x07`-`0x0f`. This can give an idea of whether the input stream
654    /// being fed to the parser is reasonable or not.
655    #[must_use]
656    pub fn errors(&self) -> usize {
657        self.errors
658    }
659
660    /// Returns whether the alternate screen is currently in use.
661    #[must_use]
662    pub fn alternate_screen(&self) -> bool {
663        self.mode(MODE_ALTERNATE_SCREEN)
664    }
665
666    /// Returns whether the terminal should be in application keypad mode.
667    #[must_use]
668    pub fn application_keypad(&self) -> bool {
669        self.mode(MODE_APPLICATION_KEYPAD)
670    }
671
672    /// Returns whether the terminal should be in application cursor mode.
673    #[must_use]
674    pub fn application_cursor(&self) -> bool {
675        self.mode(MODE_APPLICATION_CURSOR)
676    }
677
678    /// Returns whether the terminal should be in hide cursor mode.
679    #[must_use]
680    pub fn hide_cursor(&self) -> bool {
681        self.mode(MODE_HIDE_CURSOR)
682    }
683
684    /// Returns whether the terminal should be in bracketed paste mode.
685    #[must_use]
686    pub fn bracketed_paste(&self) -> bool {
687        self.mode(MODE_BRACKETED_PASTE)
688    }
689
690    /// Returns the currently active `MouseProtocolMode`
691    #[must_use]
692    pub fn mouse_protocol_mode(&self) -> MouseProtocolMode {
693        self.mouse_protocol_mode
694    }
695
696    /// Returns the currently active `MouseProtocolEncoding`
697    #[must_use]
698    pub fn mouse_protocol_encoding(&self) -> MouseProtocolEncoding {
699        self.mouse_protocol_encoding
700    }
701
702    /// Returns the currently active foreground color.
703    #[must_use]
704    pub fn fgcolor(&self) -> crate::attrs::Color {
705        self.attrs.fgcolor
706    }
707
708    /// Returns the currently active background color.
709    #[must_use]
710    pub fn bgcolor(&self) -> crate::attrs::Color {
711        self.attrs.bgcolor
712    }
713
714    /// Returns whether newly drawn text should be rendered with the bold text
715    /// attribute.
716    #[must_use]
717    pub fn bold(&self) -> bool {
718        self.attrs.bold()
719    }
720
721    /// Returns whether newly drawn text should be rendered with the italic
722    /// text attribute.
723    #[must_use]
724    pub fn italic(&self) -> bool {
725        self.attrs.italic()
726    }
727
728    /// Returns whether newly drawn text should be rendered with the
729    /// underlined text attribute.
730    #[must_use]
731    pub fn underline(&self) -> bool {
732        self.attrs.underline()
733    }
734
735    /// Returns whether newly drawn text should be rendered with the inverse
736    /// text attribute.
737    #[must_use]
738    pub fn inverse(&self) -> bool {
739        self.attrs.inverse()
740    }
741
742    fn grid(&self) -> &crate::grid::Grid {
743        if self.mode(MODE_ALTERNATE_SCREEN) {
744            &self.alternate_grid
745        } else {
746            &self.grid
747        }
748    }
749
750    fn grid_mut(&mut self) -> &mut crate::grid::Grid {
751        if self.mode(MODE_ALTERNATE_SCREEN) {
752            &mut self.alternate_grid
753        } else {
754            &mut self.grid
755        }
756    }
757
758    fn enter_alternate_grid(&mut self) {
759        self.grid_mut().set_scrollback(0);
760        self.set_mode(MODE_ALTERNATE_SCREEN);
761        self.alternate_grid.allocate_rows();
762    }
763
764    fn exit_alternate_grid(&mut self) {
765        self.clear_mode(MODE_ALTERNATE_SCREEN);
766    }
767
768    fn save_cursor(&mut self) {
769        self.grid_mut().save_cursor();
770        self.saved_attrs = self.attrs;
771    }
772
773    fn restore_cursor(&mut self) {
774        self.grid_mut().restore_cursor();
775        self.attrs = self.saved_attrs;
776    }
777
778    fn set_mode(&mut self, mode: u8) {
779        self.modes |= mode;
780    }
781
782    fn clear_mode(&mut self, mode: u8) {
783        self.modes &= !mode;
784    }
785
786    fn mode(&self, mode: u8) -> bool {
787        self.modes & mode != 0
788    }
789
790    fn set_mouse_mode(&mut self, mode: MouseProtocolMode) {
791        self.mouse_protocol_mode = mode;
792    }
793
794    fn clear_mouse_mode(&mut self, mode: MouseProtocolMode) {
795        if self.mouse_protocol_mode == mode {
796            self.mouse_protocol_mode = MouseProtocolMode::default();
797        }
798    }
799
800    fn set_mouse_encoding(&mut self, encoding: MouseProtocolEncoding) {
801        self.mouse_protocol_encoding = encoding;
802    }
803
804    fn clear_mouse_encoding(&mut self, encoding: MouseProtocolEncoding) {
805        if self.mouse_protocol_encoding == encoding {
806            self.mouse_protocol_encoding = MouseProtocolEncoding::default();
807        }
808    }
809}
810
811impl Screen {
812    fn text(&mut self, c: char) {
813        let pos = self.grid().pos();
814        let size = self.grid().size();
815        let attrs = self.attrs;
816
817        let width = c.width();
818        if width.is_none() && (u32::from(c)) < 256 {
819            // don't even try to draw control characters
820            return;
821        }
822        let width = width
823            .unwrap_or(1)
824            .try_into()
825            // width() can only return 0, 1, or 2
826            .unwrap();
827
828        // it doesn't make any sense to wrap if the last column in a row
829        // didn't already have contents. don't try to handle the case where a
830        // character wraps because there was only one column left in the
831        // previous row - literally everything handles this case differently,
832        // and this is tmux behavior (and also the simplest). i'm open to
833        // reconsidering this behavior, but only with a really good reason
834        // (xterm handles this by introducing the concept of triple width
835        // cells, which i really don't want to do).
836        let mut wrap = false;
837        if pos.col > size.cols - width {
838            let last_cell = self
839                .grid()
840                .drawing_cell(crate::grid::Pos {
841                    row: pos.row,
842                    col: size.cols - 1,
843                })
844                // pos.row is valid, since it comes directly from
845                // self.grid().pos() which we assume to always have a valid
846                // row value. size.cols - 1 is also always a valid column.
847                .unwrap();
848            if last_cell.has_contents() || last_cell.is_wide_continuation() {
849                wrap = true;
850            }
851        }
852        self.grid_mut().col_wrap(width, wrap);
853        let pos = self.grid().pos();
854
855        if width == 0 {
856            if pos.col > 0 {
857                let mut prev_cell = self
858                    .grid_mut()
859                    .drawing_cell_mut(crate::grid::Pos {
860                        row: pos.row,
861                        col: pos.col - 1,
862                    })
863                    // pos.row is valid, since it comes directly from
864                    // self.grid().pos() which we assume to always have a
865                    // valid row value. pos.col - 1 is valid because we just
866                    // checked for pos.col > 0.
867                    .unwrap();
868                if prev_cell.is_wide_continuation() {
869                    prev_cell = self
870                        .grid_mut()
871                        .drawing_cell_mut(crate::grid::Pos {
872                            row: pos.row,
873                            col: pos.col - 2,
874                        })
875                        // pos.row is valid, since it comes directly from
876                        // self.grid().pos() which we assume to always have a
877                        // valid row value. we know pos.col - 2 is valid
878                        // because the cell at pos.col - 1 is a wide
879                        // continuation character, which means there must be
880                        // the first half of the wide character before it.
881                        .unwrap();
882                }
883                prev_cell.append(c);
884            } else if pos.row > 0 {
885                let prev_row = self
886                    .grid()
887                    .drawing_row(pos.row - 1)
888                    // pos.row is valid, since it comes directly from
889                    // self.grid().pos() which we assume to always have a
890                    // valid row value. pos.row - 1 is valid because we just
891                    // checked for pos.row > 0.
892                    .unwrap();
893                if prev_row.wrapped() {
894                    let mut prev_cell = self
895                        .grid_mut()
896                        .drawing_cell_mut(crate::grid::Pos {
897                            row: pos.row - 1,
898                            col: size.cols - 1,
899                        })
900                        // pos.row is valid, since it comes directly from
901                        // self.grid().pos() which we assume to always have a
902                        // valid row value. pos.row - 1 is valid because we
903                        // just checked for pos.row > 0. col of size.cols - 1
904                        // is always valid.
905                        .unwrap();
906                    if prev_cell.is_wide_continuation() {
907                        prev_cell = self
908                            .grid_mut()
909                            .drawing_cell_mut(crate::grid::Pos {
910                                row: pos.row - 1,
911                                col: size.cols - 2,
912                            })
913                            // pos.row is valid, since it comes directly from
914                            // self.grid().pos() which we assume to always
915                            // have a valid row value. pos.row - 1 is valid
916                            // because we just checked for pos.row > 0. col of
917                            // size.cols - 2 is valid because the cell at
918                            // size.cols - 1 is a wide continuation character,
919                            // so it must have the first half of the wide
920                            // character before it.
921                            .unwrap();
922                    }
923                    prev_cell.append(c);
924                }
925            }
926        } else {
927            if self
928                .grid()
929                .drawing_cell(pos)
930                // pos.row is valid because we assume self.grid().pos() to
931                // always have a valid row value. pos.col is valid because we
932                // called col_wrap() immediately before this, which ensures
933                // that self.grid().pos().col has a valid value.
934                .unwrap()
935                .is_wide_continuation()
936            {
937                let prev_cell = self
938                    .grid_mut()
939                    .drawing_cell_mut(crate::grid::Pos {
940                        row: pos.row,
941                        col: pos.col - 1,
942                    })
943                    // pos.row is valid because we assume self.grid().pos() to
944                    // always have a valid row value. pos.col is valid because
945                    // we called col_wrap() immediately before this, which
946                    // ensures that self.grid().pos().col has a valid value.
947                    // pos.col - 1 is valid because the cell at pos.col is a
948                    // wide continuation character, so it must have the first
949                    // half of the wide character before it.
950                    .unwrap();
951                prev_cell.clear(attrs);
952            }
953
954            if self
955                .grid()
956                .drawing_cell(pos)
957                // pos.row is valid because we assume self.grid().pos() to
958                // always have a valid row value. pos.col is valid because we
959                // called col_wrap() immediately before this, which ensures
960                // that self.grid().pos().col has a valid value.
961                .unwrap()
962                .is_wide()
963            {
964                let next_cell = self
965                    .grid_mut()
966                    .drawing_cell_mut(crate::grid::Pos {
967                        row: pos.row,
968                        col: pos.col + 1,
969                    })
970                    // pos.row is valid because we assume self.grid().pos() to
971                    // always have a valid row value. pos.col is valid because
972                    // we called col_wrap() immediately before this, which
973                    // ensures that self.grid().pos().col has a valid value.
974                    // pos.col + 1 is valid because the cell at pos.col is a
975                    // wide character, so it must have the second half of the
976                    // wide character after it.
977                    .unwrap();
978                next_cell.set(' ', attrs);
979            }
980
981            let cell = self
982                .grid_mut()
983                .drawing_cell_mut(pos)
984                // pos.row is valid because we assume self.grid().pos() to
985                // always have a valid row value. pos.col is valid because we
986                // called col_wrap() immediately before this, which ensures
987                // that self.grid().pos().col has a valid value.
988                .unwrap();
989            cell.set(c, attrs);
990            self.grid_mut().col_inc(1);
991            if width > 1 {
992                let pos = self.grid().pos();
993                if self
994                    .grid()
995                    .drawing_cell(pos)
996                    // pos.row is valid because we assume self.grid().pos() to
997                    // always have a valid row value. pos.col is valid because
998                    // we called col_wrap() earlier, which ensures that
999                    // self.grid().pos().col has a valid value. this is true
1000                    // even though we just called col_inc, because this branch
1001                    // only happens if width > 1, and col_wrap takes width
1002                    // into account.
1003                    .unwrap()
1004                    .is_wide()
1005                {
1006                    let next_next_pos = crate::grid::Pos {
1007                        row: pos.row,
1008                        col: pos.col + 1,
1009                    };
1010                    let next_next_cell = self
1011                        .grid_mut()
1012                        .drawing_cell_mut(next_next_pos)
1013                        // pos.row is valid because we assume
1014                        // self.grid().pos() to always have a valid row value.
1015                        // pos.col is valid because we called col_wrap()
1016                        // earlier, which ensures that self.grid().pos().col
1017                        // has a valid value. this is true even though we just
1018                        // called col_inc, because this branch only happens if
1019                        // width > 1, and col_wrap takes width into account.
1020                        // pos.col + 1 is valid because the cell at pos.col is
1021                        // wide, and so it must have the second half of the
1022                        // wide character after it.
1023                        .unwrap();
1024                    next_next_cell.clear(attrs);
1025                    if next_next_pos.col == size.cols - 1 {
1026                        self.grid_mut()
1027                            .drawing_row_mut(pos.row)
1028                            // we assume self.grid().pos().row is always valid
1029                            .unwrap()
1030                            .wrap(false);
1031                    }
1032                }
1033                let next_cell = self
1034                    .grid_mut()
1035                    .drawing_cell_mut(pos)
1036                    // pos.row is valid because we assume self.grid().pos() to
1037                    // always have a valid row value. pos.col is valid because
1038                    // we called col_wrap() earlier, which ensures that
1039                    // self.grid().pos().col has a valid value. this is true
1040                    // even though we just called col_inc, because this branch
1041                    // only happens if width > 1, and col_wrap takes width
1042                    // into account.
1043                    .unwrap();
1044                next_cell.clear(crate::attrs::Attrs::default());
1045                next_cell.set_wide_continuation(true);
1046                self.grid_mut().col_inc(1);
1047            }
1048        }
1049    }
1050
1051    // control codes
1052
1053    fn bel(&mut self) {
1054        self.audible_bell_count += 1;
1055    }
1056
1057    fn bs(&mut self) {
1058        self.grid_mut().col_dec(1);
1059    }
1060
1061    fn tab(&mut self) {
1062        self.grid_mut().col_tab();
1063    }
1064
1065    fn lf(&mut self) {
1066        self.grid_mut().row_inc_scroll(1);
1067    }
1068
1069    fn vt(&mut self) {
1070        self.lf();
1071    }
1072
1073    fn ff(&mut self) {
1074        self.lf();
1075    }
1076
1077    fn cr(&mut self) {
1078        self.grid_mut().col_set(0);
1079    }
1080
1081    // escape codes
1082
1083    // ESC 7
1084    fn decsc(&mut self) {
1085        self.save_cursor();
1086    }
1087
1088    // ESC 8
1089    fn decrc(&mut self) {
1090        self.restore_cursor();
1091    }
1092
1093    // ESC =
1094    fn deckpam(&mut self) {
1095        self.set_mode(MODE_APPLICATION_KEYPAD);
1096    }
1097
1098    // ESC >
1099    fn deckpnm(&mut self) {
1100        self.clear_mode(MODE_APPLICATION_KEYPAD);
1101    }
1102
1103    // ESC M
1104    fn ri(&mut self) {
1105        self.grid_mut().row_dec_scroll(1);
1106    }
1107
1108    // ESC c
1109    fn ris(&mut self) {
1110        let title = self.title.clone();
1111        let icon_name = self.icon_name.clone();
1112        let audible_bell_count = self.audible_bell_count;
1113        let visual_bell_count = self.visual_bell_count;
1114        let errors = self.errors;
1115
1116        *self = Self::new(self.grid.size(), self.grid.scrollback_len());
1117
1118        self.title = title;
1119        self.icon_name = icon_name;
1120        self.audible_bell_count = audible_bell_count;
1121        self.visual_bell_count = visual_bell_count;
1122        self.errors = errors;
1123    }
1124
1125    // ESC g
1126    fn vb(&mut self) {
1127        self.visual_bell_count += 1;
1128    }
1129
1130    // csi codes
1131
1132    // CSI @
1133    fn ich(&mut self, count: u16) {
1134        self.grid_mut().insert_cells(count);
1135    }
1136
1137    // CSI A
1138    fn cuu(&mut self, offset: u16) {
1139        self.grid_mut().row_dec_clamp(offset);
1140    }
1141
1142    // CSI B
1143    fn cud(&mut self, offset: u16) {
1144        self.grid_mut().row_inc_clamp(offset);
1145    }
1146
1147    // CSI C
1148    fn cuf(&mut self, offset: u16) {
1149        self.grid_mut().col_inc_clamp(offset);
1150    }
1151
1152    // CSI D
1153    fn cub(&mut self, offset: u16) {
1154        self.grid_mut().col_dec(offset);
1155    }
1156
1157    // CSI G
1158    fn cha(&mut self, col: u16) {
1159        self.grid_mut().col_set(col - 1);
1160    }
1161
1162    // CSI H
1163    fn cup(&mut self, (row, col): (u16, u16)) {
1164        self.grid_mut().set_pos(crate::grid::Pos {
1165            row: row - 1,
1166            col: col - 1,
1167        });
1168    }
1169
1170    // CSI J
1171    fn ed(&mut self, mode: u16) {
1172        let attrs = self.attrs;
1173        match mode {
1174            0 => self.grid_mut().erase_all_forward(attrs),
1175            1 => self.grid_mut().erase_all_backward(attrs),
1176            2 => self.grid_mut().erase_all(attrs),
1177            n => {
1178                log::debug!("unhandled ED mode: {n}");
1179            }
1180        }
1181    }
1182
1183    // CSI ? J
1184    fn decsed(&mut self, mode: u16) {
1185        self.ed(mode);
1186    }
1187
1188    // CSI K
1189    fn el(&mut self, mode: u16) {
1190        let attrs = self.attrs;
1191        match mode {
1192            0 => self.grid_mut().erase_row_forward(attrs),
1193            1 => self.grid_mut().erase_row_backward(attrs),
1194            2 => self.grid_mut().erase_row(attrs),
1195            n => {
1196                log::debug!("unhandled EL mode: {n}");
1197            }
1198        }
1199    }
1200
1201    // CSI ? K
1202    fn decsel(&mut self, mode: u16) {
1203        self.el(mode);
1204    }
1205
1206    // CSI L
1207    fn il(&mut self, count: u16) {
1208        self.grid_mut().insert_lines(count);
1209    }
1210
1211    // CSI M
1212    fn dl(&mut self, count: u16) {
1213        self.grid_mut().delete_lines(count);
1214    }
1215
1216    // CSI P
1217    fn dch(&mut self, count: u16) {
1218        self.grid_mut().delete_cells(count);
1219    }
1220
1221    // CSI S
1222    fn su(&mut self, count: u16) {
1223        self.grid_mut().scroll_up(count);
1224    }
1225
1226    // CSI T
1227    fn sd(&mut self, count: u16) {
1228        self.grid_mut().scroll_down(count);
1229    }
1230
1231    // CSI X
1232    fn ech(&mut self, count: u16) {
1233        let attrs = self.attrs;
1234        self.grid_mut().erase_cells(count, attrs);
1235    }
1236
1237    // CSI d
1238    fn vpa(&mut self, row: u16) {
1239        self.grid_mut().row_set(row - 1);
1240    }
1241
1242    // CSI h
1243    #[allow(clippy::unused_self)]
1244    fn sm(&mut self, params: &vte::Params) {
1245        // nothing, i think?
1246        if log::log_enabled!(log::Level::Debug) {
1247            log::debug!("unhandled SM mode: {}", param_str(params));
1248        }
1249    }
1250
1251    // CSI ? h
1252    fn decset(&mut self, params: &vte::Params) {
1253        for param in params {
1254            match param {
1255                &[1] => self.set_mode(MODE_APPLICATION_CURSOR),
1256                &[6] => self.grid_mut().set_origin_mode(true),
1257                &[9] => self.set_mouse_mode(MouseProtocolMode::Press),
1258                &[25] => self.clear_mode(MODE_HIDE_CURSOR),
1259                &[47] => self.enter_alternate_grid(),
1260                &[1000] => {
1261                    self.set_mouse_mode(MouseProtocolMode::PressRelease);
1262                }
1263                &[1002] => {
1264                    self.set_mouse_mode(MouseProtocolMode::ButtonMotion);
1265                }
1266                &[1003] => self.set_mouse_mode(MouseProtocolMode::AnyMotion),
1267                &[1005] => {
1268                    self.set_mouse_encoding(MouseProtocolEncoding::Utf8);
1269                }
1270                &[1006] => {
1271                    self.set_mouse_encoding(MouseProtocolEncoding::Sgr);
1272                }
1273                &[1049] => {
1274                    self.decsc();
1275                    self.alternate_grid.clear();
1276                    self.enter_alternate_grid();
1277                }
1278                &[2004] => self.set_mode(MODE_BRACKETED_PASTE),
1279                ns => {
1280                    if log::log_enabled!(log::Level::Debug) {
1281                        let n = if ns.len() == 1 {
1282                            format!(
1283                                "{}",
1284                                // we just checked that ns.len() == 1, so 0
1285                                // must be valid
1286                                ns[0]
1287                            )
1288                        } else {
1289                            format!("{ns:?}")
1290                        };
1291                        log::debug!("unhandled DECSET mode: {n}");
1292                    }
1293                }
1294            }
1295        }
1296    }
1297
1298    // CSI l
1299    #[allow(clippy::unused_self)]
1300    fn rm(&mut self, params: &vte::Params) {
1301        // nothing, i think?
1302        if log::log_enabled!(log::Level::Debug) {
1303            log::debug!("unhandled RM mode: {}", param_str(params));
1304        }
1305    }
1306
1307    // CSI ? l
1308    fn decrst(&mut self, params: &vte::Params) {
1309        for param in params {
1310            match param {
1311                &[1] => self.clear_mode(MODE_APPLICATION_CURSOR),
1312                &[6] => self.grid_mut().set_origin_mode(false),
1313                &[9] => self.clear_mouse_mode(MouseProtocolMode::Press),
1314                &[25] => self.set_mode(MODE_HIDE_CURSOR),
1315                &[47] => {
1316                    self.exit_alternate_grid();
1317                }
1318                &[1000] => {
1319                    self.clear_mouse_mode(MouseProtocolMode::PressRelease);
1320                }
1321                &[1002] => {
1322                    self.clear_mouse_mode(MouseProtocolMode::ButtonMotion);
1323                }
1324                &[1003] => {
1325                    self.clear_mouse_mode(MouseProtocolMode::AnyMotion);
1326                }
1327                &[1005] => {
1328                    self.clear_mouse_encoding(MouseProtocolEncoding::Utf8);
1329                }
1330                &[1006] => {
1331                    self.clear_mouse_encoding(MouseProtocolEncoding::Sgr);
1332                }
1333                &[1049] => {
1334                    self.exit_alternate_grid();
1335                    self.decrc();
1336                }
1337                &[2004] => self.clear_mode(MODE_BRACKETED_PASTE),
1338                ns => {
1339                    if log::log_enabled!(log::Level::Debug) {
1340                        let n = if ns.len() == 1 {
1341                            format!(
1342                                "{}",
1343                                // we just checked that ns.len() == 1, so 0
1344                                // must be valid
1345                                ns[0]
1346                            )
1347                        } else {
1348                            format!("{ns:?}")
1349                        };
1350                        log::debug!("unhandled DECRST mode: {n}");
1351                    }
1352                }
1353            }
1354        }
1355    }
1356
1357    // CSI m
1358    fn sgr(&mut self, params: &vte::Params) {
1359        // XXX really i want to just be able to pass in a default Params
1360        // instance with a 0 in it, but vte doesn't allow creating new Params
1361        // instances
1362        if params.is_empty() {
1363            self.attrs = crate::attrs::Attrs::default();
1364            return;
1365        }
1366
1367        let mut iter = params.iter();
1368
1369        macro_rules! next_param {
1370            () => {
1371                match iter.next() {
1372                    Some(n) => n,
1373                    _ => return,
1374                }
1375            };
1376        }
1377
1378        macro_rules! to_u8 {
1379            ($n:expr) => {
1380                if let Some(n) = u16_to_u8($n) {
1381                    n
1382                } else {
1383                    return;
1384                }
1385            };
1386        }
1387
1388        macro_rules! next_param_u8 {
1389            () => {
1390                if let &[n] = next_param!() {
1391                    to_u8!(n)
1392                } else {
1393                    return;
1394                }
1395            };
1396        }
1397
1398        loop {
1399            match next_param!() {
1400                &[0] => self.attrs = crate::attrs::Attrs::default(),
1401                &[1] => self.attrs.set_bold(true),
1402                &[3] => self.attrs.set_italic(true),
1403                &[4] => self.attrs.set_underline(true),
1404                &[7] => self.attrs.set_inverse(true),
1405                &[22] => self.attrs.set_bold(false),
1406                &[23] => self.attrs.set_italic(false),
1407                &[24] => self.attrs.set_underline(false),
1408                &[27] => self.attrs.set_inverse(false),
1409                &[n] if (30..=37).contains(&n) => {
1410                    self.attrs.fgcolor =
1411                        crate::attrs::Color::Idx(to_u8!(n) - 30);
1412                }
1413                &[38, 2, r, g, b] => {
1414                    self.attrs.fgcolor = crate::attrs::Color::Rgb(
1415                        to_u8!(r),
1416                        to_u8!(g),
1417                        to_u8!(b),
1418                    );
1419                }
1420                &[38, 5, i] => {
1421                    self.attrs.fgcolor = crate::attrs::Color::Idx(to_u8!(i));
1422                }
1423                &[38] => match next_param!() {
1424                    &[2] => {
1425                        let r = next_param_u8!();
1426                        let g = next_param_u8!();
1427                        let b = next_param_u8!();
1428                        self.attrs.fgcolor =
1429                            crate::attrs::Color::Rgb(r, g, b);
1430                    }
1431                    &[5] => {
1432                        self.attrs.fgcolor =
1433                            crate::attrs::Color::Idx(next_param_u8!());
1434                    }
1435                    ns => {
1436                        if log::log_enabled!(log::Level::Debug) {
1437                            let n = if ns.len() == 1 {
1438                                format!(
1439                                    "{}",
1440                                    // we just checked that ns.len() == 1, so
1441                                    // 0 must be valid
1442                                    ns[0]
1443                                )
1444                            } else {
1445                                format!("{ns:?}")
1446                            };
1447                            log::debug!("unhandled SGR mode: 38 {n}");
1448                        }
1449                        return;
1450                    }
1451                },
1452                &[39] => {
1453                    self.attrs.fgcolor = crate::attrs::Color::Default;
1454                }
1455                &[n] if (40..=47).contains(&n) => {
1456                    self.attrs.bgcolor =
1457                        crate::attrs::Color::Idx(to_u8!(n) - 40);
1458                }
1459                &[48, 2, r, g, b] => {
1460                    self.attrs.bgcolor = crate::attrs::Color::Rgb(
1461                        to_u8!(r),
1462                        to_u8!(g),
1463                        to_u8!(b),
1464                    );
1465                }
1466                &[48, 5, i] => {
1467                    self.attrs.bgcolor = crate::attrs::Color::Idx(to_u8!(i));
1468                }
1469                &[48] => match next_param!() {
1470                    &[2] => {
1471                        let r = next_param_u8!();
1472                        let g = next_param_u8!();
1473                        let b = next_param_u8!();
1474                        self.attrs.bgcolor =
1475                            crate::attrs::Color::Rgb(r, g, b);
1476                    }
1477                    &[5] => {
1478                        self.attrs.bgcolor =
1479                            crate::attrs::Color::Idx(next_param_u8!());
1480                    }
1481                    ns => {
1482                        if log::log_enabled!(log::Level::Debug) {
1483                            let n = if ns.len() == 1 {
1484                                format!(
1485                                    "{}",
1486                                    // we just checked that ns.len() == 1, so
1487                                    // 0 must be valid
1488                                    ns[0]
1489                                )
1490                            } else {
1491                                format!("{ns:?}")
1492                            };
1493                            log::debug!("unhandled SGR mode: 48 {n}");
1494                        }
1495                        return;
1496                    }
1497                },
1498                &[49] => {
1499                    self.attrs.bgcolor = crate::attrs::Color::Default;
1500                }
1501                &[n] if (90..=97).contains(&n) => {
1502                    self.attrs.fgcolor =
1503                        crate::attrs::Color::Idx(to_u8!(n) - 82);
1504                }
1505                &[n] if (100..=107).contains(&n) => {
1506                    self.attrs.bgcolor =
1507                        crate::attrs::Color::Idx(to_u8!(n) - 92);
1508                }
1509                ns => {
1510                    if log::log_enabled!(log::Level::Debug) {
1511                        let n = if ns.len() == 1 {
1512                            format!(
1513                                "{}",
1514                                // we just checked that ns.len() == 1, so 0
1515                                // must be valid
1516                                ns[0]
1517                            )
1518                        } else {
1519                            format!("{ns:?}")
1520                        };
1521                        log::debug!("unhandled SGR mode: {n}");
1522                    }
1523                }
1524            }
1525        }
1526    }
1527
1528    // CSI r
1529    fn decstbm(&mut self, (top, bottom): (u16, u16)) {
1530        self.grid_mut().set_scroll_region(top - 1, bottom - 1);
1531    }
1532
1533    // osc codes
1534
1535    fn osc0(&mut self, s: &[u8]) {
1536        self.osc1(s);
1537        self.osc2(s);
1538    }
1539
1540    fn osc1(&mut self, s: &[u8]) {
1541        if let Ok(s) = std::str::from_utf8(s) {
1542            self.icon_name = s.to_string();
1543        }
1544    }
1545
1546    fn osc2(&mut self, s: &[u8]) {
1547        if let Ok(s) = std::str::from_utf8(s) {
1548            self.title = s.to_string();
1549        }
1550    }
1551}
1552
1553impl vte::Perform for Screen {
1554    fn print(&mut self, c: char) {
1555        if c == '\u{fffd}' || ('\u{80}'..'\u{a0}').contains(&c) {
1556            self.errors = self.errors.saturating_add(1);
1557        }
1558        self.text(c);
1559    }
1560
1561    fn execute(&mut self, b: u8) {
1562        match b {
1563            7 => self.bel(),
1564            8 => self.bs(),
1565            9 => self.tab(),
1566            10 => self.lf(),
1567            11 => self.vt(),
1568            12 => self.ff(),
1569            13 => self.cr(),
1570            // we don't implement shift in/out alternate character sets, but
1571            // it shouldn't count as an "error"
1572            14 | 15 => {}
1573            _ => {
1574                self.errors = self.errors.saturating_add(1);
1575                log::debug!("unhandled control character: {b}");
1576            }
1577        }
1578    }
1579
1580    fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, b: u8) {
1581        intermediates.first().map_or_else(
1582            || match b {
1583                b'7' => self.decsc(),
1584                b'8' => self.decrc(),
1585                b'=' => self.deckpam(),
1586                b'>' => self.deckpnm(),
1587                b'M' => self.ri(),
1588                b'c' => self.ris(),
1589                b'g' => self.vb(),
1590                _ => {
1591                    log::debug!("unhandled escape code: ESC {b}");
1592                }
1593            },
1594            |i| {
1595                log::debug!("unhandled escape code: ESC {i} {b}");
1596            },
1597        );
1598    }
1599
1600    fn csi_dispatch(
1601        &mut self,
1602        params: &vte::Params,
1603        intermediates: &[u8],
1604        _ignore: bool,
1605        c: char,
1606    ) {
1607        match intermediates.first() {
1608            None => match c {
1609                '@' => self.ich(canonicalize_params_1(params, 1)),
1610                'A' => self.cuu(canonicalize_params_1(params, 1)),
1611                'B' => self.cud(canonicalize_params_1(params, 1)),
1612                'C' => self.cuf(canonicalize_params_1(params, 1)),
1613                'D' => self.cub(canonicalize_params_1(params, 1)),
1614                'G' => self.cha(canonicalize_params_1(params, 1)),
1615                'H' => self.cup(canonicalize_params_2(params, 1, 1)),
1616                'J' => self.ed(canonicalize_params_1(params, 0)),
1617                'K' => self.el(canonicalize_params_1(params, 0)),
1618                'L' => self.il(canonicalize_params_1(params, 1)),
1619                'M' => self.dl(canonicalize_params_1(params, 1)),
1620                'P' => self.dch(canonicalize_params_1(params, 1)),
1621                'S' => self.su(canonicalize_params_1(params, 1)),
1622                'T' => self.sd(canonicalize_params_1(params, 1)),
1623                'X' => self.ech(canonicalize_params_1(params, 1)),
1624                'd' => self.vpa(canonicalize_params_1(params, 1)),
1625                'h' => self.sm(params),
1626                'l' => self.rm(params),
1627                'm' => self.sgr(params),
1628                'r' => self.decstbm(canonicalize_params_decstbm(
1629                    params,
1630                    self.grid().size(),
1631                )),
1632                _ => {
1633                    if log::log_enabled!(log::Level::Debug) {
1634                        log::debug!(
1635                            "unhandled csi sequence: CSI {} {}",
1636                            param_str(params),
1637                            c
1638                        );
1639                    }
1640                }
1641            },
1642            Some(b'?') => match c {
1643                'J' => self.decsed(canonicalize_params_1(params, 0)),
1644                'K' => self.decsel(canonicalize_params_1(params, 0)),
1645                'h' => self.decset(params),
1646                'l' => self.decrst(params),
1647                _ => {
1648                    if log::log_enabled!(log::Level::Debug) {
1649                        log::debug!(
1650                            "unhandled csi sequence: CSI ? {} {}",
1651                            param_str(params),
1652                            c
1653                        );
1654                    }
1655                }
1656            },
1657            Some(i) => {
1658                if log::log_enabled!(log::Level::Debug) {
1659                    log::debug!(
1660                        "unhandled csi sequence: CSI {} {} {}",
1661                        i,
1662                        param_str(params),
1663                        c
1664                    );
1665                }
1666            }
1667        }
1668    }
1669
1670    fn osc_dispatch(&mut self, params: &[&[u8]], _bel_terminated: bool) {
1671        match (params.get(0), params.get(1)) {
1672            (Some(&b"0"), Some(s)) => self.osc0(s),
1673            (Some(&b"1"), Some(s)) => self.osc1(s),
1674            (Some(&b"2"), Some(s)) => self.osc2(s),
1675            _ => {
1676                if log::log_enabled!(log::Level::Debug) {
1677                    log::debug!(
1678                        "unhandled osc sequence: OSC {}",
1679                        osc_param_str(params),
1680                    );
1681                }
1682            }
1683        }
1684    }
1685
1686    fn hook(
1687        &mut self,
1688        params: &vte::Params,
1689        intermediates: &[u8],
1690        _ignore: bool,
1691        action: char,
1692    ) {
1693        if log::log_enabled!(log::Level::Debug) {
1694            intermediates.first().map_or_else(
1695                || {
1696                    log::debug!(
1697                        "unhandled dcs sequence: DCS {} {}",
1698                        param_str(params),
1699                        action,
1700                    );
1701                },
1702                |i| {
1703                    log::debug!(
1704                        "unhandled dcs sequence: DCS {} {} {}",
1705                        i,
1706                        param_str(params),
1707                        action,
1708                    );
1709                },
1710            );
1711        }
1712    }
1713}
1714
1715fn canonicalize_params_1(params: &vte::Params, default: u16) -> u16 {
1716    let first = params.iter().next().map_or(0, |x| *x.first().unwrap_or(&0));
1717    if first == 0 {
1718        default
1719    } else {
1720        first
1721    }
1722}
1723
1724fn canonicalize_params_2(
1725    params: &vte::Params,
1726    default1: u16,
1727    default2: u16,
1728) -> (u16, u16) {
1729    let mut iter = params.iter();
1730    let first = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
1731    let first = if first == 0 { default1 } else { first };
1732
1733    let second = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
1734    let second = if second == 0 { default2 } else { second };
1735
1736    (first, second)
1737}
1738
1739fn canonicalize_params_decstbm(
1740    params: &vte::Params,
1741    size: crate::grid::Size,
1742) -> (u16, u16) {
1743    let mut iter = params.iter();
1744    let top = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
1745    let top = if top == 0 { 1 } else { top };
1746
1747    let bottom = iter.next().map_or(0, |x| *x.first().unwrap_or(&0));
1748    let bottom = if bottom == 0 { size.rows } else { bottom };
1749
1750    (top, bottom)
1751}
1752
1753fn u16_to_u8(i: u16) -> Option<u8> {
1754    if i > u16::from(u8::max_value()) {
1755        None
1756    } else {
1757        // safe because we just ensured that the value fits in a u8
1758        Some(i.try_into().unwrap())
1759    }
1760}
1761
1762fn param_str(params: &vte::Params) -> String {
1763    let strs: Vec<_> = params
1764        .iter()
1765        .map(|subparams| {
1766            let subparam_strs: Vec<_> = subparams
1767                .iter()
1768                .map(std::string::ToString::to_string)
1769                .collect();
1770            subparam_strs.join(" : ")
1771        })
1772        .collect();
1773    strs.join(" ; ")
1774}
1775
1776fn osc_param_str(params: &[&[u8]]) -> String {
1777    let strs: Vec<_> = params
1778        .iter()
1779        .map(|b| format!("\"{}\"", std::string::String::from_utf8_lossy(b)))
1780        .collect();
1781    strs.join(" ; ")
1782}