Skip to main content

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