Skip to main content

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, Default)]
12pub enum MouseProtocolMode {
13    /// Mouse handling is disabled.
14    #[default]
15    None,
16
17    /// Mouse button events should be reported on button press. Also known as
18    /// X10 mouse mode.
19    Press,
20
21    /// Mouse button events should be reported on button press and release.
22    /// Also known as VT200 mouse mode.
23    PressRelease,
24
25    // Highlight,
26    /// Mouse button events should be reported on button press and release, as
27    /// well as when the mouse moves between cells while a button is held
28    /// down.
29    ButtonMotion,
30
31    /// Mouse button events should be reported on button press and release,
32    /// and mouse motion events should be reported when the mouse moves
33    /// between cells regardless of whether a button is held down or not.
34    AnyMotion,
35    // DecLocator,
36}
37
38/// The encoding to use for the enabled [`MouseProtocolMode`].
39#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
40pub enum MouseProtocolEncoding {
41    /// Default single-printable-byte encoding.
42    #[default]
43    Default,
44
45    /// UTF-8-based encoding.
46    Utf8,
47
48    /// SGR-like encoding.
49    Sgr,
50    // Urxvt,
51}
52
53/// Represents the overall terminal state.
54#[derive(Clone, Debug)]
55pub struct Screen {
56    grid: crate::grid::Grid,
57    alternate_grid: crate::grid::Grid,
58
59    attrs: crate::attrs::Attrs,
60    saved_attrs: crate::attrs::Attrs,
61
62    modes: u8,
63    mouse_protocol_mode: MouseProtocolMode,
64    mouse_protocol_encoding: MouseProtocolEncoding,
65}
66
67impl Screen {
68    pub(crate) fn new(
69        size: crate::grid::Size,
70        scrollback_len: usize,
71    ) -> Self {
72        let mut grid = crate::grid::Grid::new(size, scrollback_len);
73        grid.allocate_rows();
74        Self {
75            grid,
76            alternate_grid: crate::grid::Grid::new(size, 0),
77
78            attrs: crate::attrs::Attrs::default(),
79            saved_attrs: crate::attrs::Attrs::default(),
80
81            modes: 0,
82            mouse_protocol_mode: MouseProtocolMode::default(),
83            mouse_protocol_encoding: MouseProtocolEncoding::default(),
84        }
85    }
86
87    /// Resizes the terminal.
88    pub fn set_size(&mut self, rows: u16, cols: u16) {
89        self.grid.set_size(crate::grid::Size { rows, cols });
90        self.alternate_grid
91            .set_size(crate::grid::Size { rows, cols });
92    }
93
94    /// Returns the current size of the terminal.
95    ///
96    /// The return value will be (rows, cols).
97    #[must_use]
98    pub fn size(&self) -> (u16, u16) {
99        let size = self.grid().size();
100        (size.rows, size.cols)
101    }
102
103    /// Returns the number of lines in the scrollback.
104    #[must_use]
105    pub fn scrollback_len(&self) -> usize {
106        self.grid().scrollback_len()
107    }
108
109    /// Scrolls to the given position in the scrollback.
110    ///
111    /// This position indicates the offset from the top of the screen, and
112    /// should be `0` to put the normal screen in view.
113    ///
114    /// This affects the return values of methods called on the screen: for
115    /// instance, `screen.cell(0, 0)` will return the top left corner of the
116    /// screen after taking the scrollback offset into account.
117    ///
118    /// The value given will be clamped to the actual size of the scrollback.
119    pub fn set_scrollback(&mut self, rows: usize) {
120        self.grid_mut().set_scrollback(rows);
121    }
122
123    /// Returns the current position in the scrollback.
124    ///
125    /// This position indicates the offset from the top of the screen, and is
126    /// `0` when the normal screen is in view.
127    #[must_use]
128    pub fn scrollback(&self) -> usize {
129        self.grid().scrollback()
130    }
131
132    /// Returns the text contents of the terminal.
133    ///
134    /// This will not include any formatting information, and will be in plain
135    /// text format.
136    #[must_use]
137    pub fn contents(&self) -> String {
138        let mut contents = String::new();
139        self.write_contents(&mut contents);
140        contents
141    }
142
143    fn write_contents(&self, contents: &mut String) {
144        self.grid().write_contents(contents);
145    }
146
147    /// Returns the text contents of the terminal by row, restricted to the
148    /// given subset of columns.
149    ///
150    /// This will not include any formatting information, and will be in plain
151    /// text format.
152    ///
153    /// Newlines will not be included.
154    pub fn rows(
155        &self,
156        start: u16,
157        width: u16,
158    ) -> impl Iterator<Item = String> + '_ {
159        self.grid().visible_rows().map(move |row| {
160            let mut contents = String::new();
161            row.write_contents(&mut contents, start, width, false);
162            contents
163        })
164    }
165
166    /// Returns the text contents of the terminal logically between two cells.
167    /// This will include the remainder of the starting row after `start_col`,
168    /// followed by the entire contents of the rows between `start_row` and
169    /// `end_row`, followed by the beginning of the `end_row` up until
170    /// `end_col`. This is useful for things like determining the contents of
171    /// a clipboard selection.
172    #[must_use]
173    pub fn contents_between(
174        &self,
175        start_row: u16,
176        start_col: u16,
177        end_row: u16,
178        end_col: u16,
179    ) -> String {
180        match start_row.cmp(&end_row) {
181            std::cmp::Ordering::Less => {
182                let (_, cols) = self.size();
183                let mut contents = String::new();
184                for (i, row) in self
185                    .grid()
186                    .visible_rows()
187                    .enumerate()
188                    .skip(usize::from(start_row))
189                    .take(usize::from(end_row) - usize::from(start_row) + 1)
190                {
191                    if i == usize::from(start_row) {
192                        row.write_contents(
193                            &mut contents,
194                            start_col,
195                            cols - start_col,
196                            false,
197                        );
198                        if !row.wrapped() {
199                            contents.push('\n');
200                        }
201                    } else if i == usize::from(end_row) {
202                        row.write_contents(&mut contents, 0, end_col, false);
203                    } else {
204                        row.write_contents(&mut contents, 0, cols, false);
205                        if !row.wrapped() {
206                            contents.push('\n');
207                        }
208                    }
209                }
210                contents
211            }
212            std::cmp::Ordering::Equal => {
213                if start_col < end_col {
214                    self.rows(start_col, end_col - start_col)
215                        .nth(usize::from(start_row))
216                        .unwrap_or_default()
217                } else {
218                    String::new()
219                }
220            }
221            std::cmp::Ordering::Greater => String::new(),
222        }
223    }
224
225    /// Return escape codes sufficient to reproduce the entire contents of the
226    /// current terminal state. This is a convenience wrapper around
227    /// [`contents_formatted`](Self::contents_formatted) and
228    /// [`input_mode_formatted`](Self::input_mode_formatted).
229    #[must_use]
230    pub fn state_formatted(&self) -> Vec<u8> {
231        let mut contents = vec![];
232        self.write_contents_formatted(&mut contents);
233        self.write_input_mode_formatted(&mut contents);
234        contents
235    }
236
237    /// Return escape codes sufficient to turn the terminal state of the
238    /// screen `prev` into the current terminal state. This is a convenience
239    /// wrapper around [`contents_diff`](Self::contents_diff) and
240    /// [`input_mode_diff`](Self::input_mode_diff).
241    #[must_use]
242    pub fn state_diff(&self, prev: &Self) -> Vec<u8> {
243        let mut contents = vec![];
244        self.write_contents_diff(&mut contents, prev);
245        self.write_input_mode_diff(&mut contents, prev);
246        contents
247    }
248
249    /// Returns the formatted visible contents of the terminal.
250    ///
251    /// Formatting information will be included inline as terminal escape
252    /// codes. The result will be suitable for feeding directly to a raw
253    /// terminal parser, and will result in the same visual output.
254    #[must_use]
255    pub fn contents_formatted(&self) -> Vec<u8> {
256        let mut contents = vec![];
257        self.write_contents_formatted(&mut contents);
258        contents
259    }
260
261    fn write_contents_formatted(&self, contents: &mut Vec<u8>) {
262        crate::term::HideCursor::new(self.hide_cursor()).write_buf(contents);
263        let prev_attrs = self.grid().write_contents_formatted(contents);
264        self.attrs.write_escape_code_diff(contents, &prev_attrs);
265    }
266
267    /// Returns the formatted visible contents of the terminal by row,
268    /// restricted to the given subset of columns.
269    ///
270    /// Formatting information will be included inline as terminal escape
271    /// codes. The result will be suitable for feeding directly to a raw
272    /// terminal parser, and will result in the same visual output.
273    ///
274    /// You are responsible for positioning the cursor before printing each
275    /// row, and the final cursor position after displaying each row is
276    /// unspecified.
277    // the unwraps in this method shouldn't be reachable
278    #[allow(clippy::missing_panics_doc)]
279    pub fn rows_formatted(
280        &self,
281        start: u16,
282        width: u16,
283    ) -> impl Iterator<Item = Vec<u8>> + '_ {
284        let mut wrapping = false;
285        self.grid().visible_rows().enumerate().map(move |(i, row)| {
286            // number of rows in a grid is stored in a u16 (see Size), so
287            // visible_rows can never return enough rows to overflow here
288            let i = i.try_into().unwrap();
289            let mut contents = vec![];
290            row.write_contents_formatted(
291                &mut contents,
292                start,
293                width,
294                i,
295                wrapping,
296                None,
297                None,
298            );
299            if start == 0 && width == self.grid.size().cols {
300                wrapping = row.wrapped();
301            }
302            contents
303        })
304    }
305
306    /// Returns a terminal byte stream sufficient to turn the visible contents
307    /// of the screen described by `prev` into the visible contents of the
308    /// screen described by `self`.
309    ///
310    /// The result of rendering `prev.contents_formatted()` followed by
311    /// `self.contents_diff(prev)` should be equivalent to the result of
312    /// rendering `self.contents_formatted()`. This is primarily useful when
313    /// you already have a terminal parser whose state is described by `prev`,
314    /// since the diff will likely require less memory and cause less
315    /// flickering than redrawing the entire screen contents.
316    #[must_use]
317    pub fn contents_diff(&self, prev: &Self) -> Vec<u8> {
318        let mut contents = vec![];
319        self.write_contents_diff(&mut contents, prev);
320        contents
321    }
322
323    fn write_contents_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
324        if self.hide_cursor() != prev.hide_cursor() {
325            crate::term::HideCursor::new(self.hide_cursor())
326                .write_buf(contents);
327        }
328        let prev_attrs = self.grid().write_contents_diff(
329            contents,
330            prev.grid(),
331            prev.attrs,
332        );
333        self.attrs.write_escape_code_diff(contents, &prev_attrs);
334    }
335
336    /// Returns a sequence of terminal byte streams sufficient to turn the
337    /// visible contents of the subset of each row from `prev` (as described
338    /// by `start` and `width`) into the visible contents of the corresponding
339    /// row subset in `self`.
340    ///
341    /// You are responsible for positioning the cursor before printing each
342    /// row, and the final cursor position after displaying each row is
343    /// unspecified.
344    // the unwraps in this method shouldn't be reachable
345    #[allow(clippy::missing_panics_doc)]
346    pub fn rows_diff<'a>(
347        &'a self,
348        prev: &'a Self,
349        start: u16,
350        width: u16,
351    ) -> impl Iterator<Item = Vec<u8>> + 'a {
352        self.grid()
353            .visible_rows()
354            .zip(prev.grid().visible_rows())
355            .enumerate()
356            .map(move |(i, (row, prev_row))| {
357                // number of rows in a grid is stored in a u16 (see Size), so
358                // visible_rows can never return enough rows to overflow here
359                let i = i.try_into().unwrap();
360                let mut contents = vec![];
361                row.write_contents_diff(
362                    &mut contents,
363                    prev_row,
364                    start,
365                    width,
366                    i,
367                    false,
368                    false,
369                    crate::grid::Pos { row: i, col: start },
370                    crate::attrs::Attrs::default(),
371                );
372                contents
373            })
374    }
375
376    /// Returns terminal escape sequences sufficient to set the current
377    /// terminal's input modes.
378    ///
379    /// Supported modes are:
380    /// * application keypad
381    /// * application cursor
382    /// * bracketed paste
383    /// * xterm mouse support
384    #[must_use]
385    pub fn input_mode_formatted(&self) -> Vec<u8> {
386        let mut contents = vec![];
387        self.write_input_mode_formatted(&mut contents);
388        contents
389    }
390
391    fn write_input_mode_formatted(&self, contents: &mut Vec<u8>) {
392        crate::term::ApplicationKeypad::new(
393            self.mode(MODE_APPLICATION_KEYPAD),
394        )
395        .write_buf(contents);
396        crate::term::ApplicationCursor::new(
397            self.mode(MODE_APPLICATION_CURSOR),
398        )
399        .write_buf(contents);
400        crate::term::BracketedPaste::new(self.mode(MODE_BRACKETED_PASTE))
401            .write_buf(contents);
402        crate::term::MouseProtocolMode::new(
403            self.mouse_protocol_mode,
404            MouseProtocolMode::None,
405        )
406        .write_buf(contents);
407        crate::term::MouseProtocolEncoding::new(
408            self.mouse_protocol_encoding,
409            MouseProtocolEncoding::Default,
410        )
411        .write_buf(contents);
412    }
413
414    /// Returns terminal escape sequences sufficient to change the previous
415    /// terminal's input modes to the input modes enabled in the current
416    /// terminal.
417    #[must_use]
418    pub fn input_mode_diff(&self, prev: &Self) -> Vec<u8> {
419        let mut contents = vec![];
420        self.write_input_mode_diff(&mut contents, prev);
421        contents
422    }
423
424    fn write_input_mode_diff(&self, contents: &mut Vec<u8>, prev: &Self) {
425        if self.mode(MODE_APPLICATION_KEYPAD)
426            != prev.mode(MODE_APPLICATION_KEYPAD)
427        {
428            crate::term::ApplicationKeypad::new(
429                self.mode(MODE_APPLICATION_KEYPAD),
430            )
431            .write_buf(contents);
432        }
433        if self.mode(MODE_APPLICATION_CURSOR)
434            != prev.mode(MODE_APPLICATION_CURSOR)
435        {
436            crate::term::ApplicationCursor::new(
437                self.mode(MODE_APPLICATION_CURSOR),
438            )
439            .write_buf(contents);
440        }
441        if self.mode(MODE_BRACKETED_PASTE) != prev.mode(MODE_BRACKETED_PASTE)
442        {
443            crate::term::BracketedPaste::new(self.mode(MODE_BRACKETED_PASTE))
444                .write_buf(contents);
445        }
446        crate::term::MouseProtocolMode::new(
447            self.mouse_protocol_mode,
448            prev.mouse_protocol_mode,
449        )
450        .write_buf(contents);
451        crate::term::MouseProtocolEncoding::new(
452            self.mouse_protocol_encoding,
453            prev.mouse_protocol_encoding,
454        )
455        .write_buf(contents);
456    }
457
458    /// Returns terminal escape sequences sufficient to set the current
459    /// terminal's drawing attributes.
460    ///
461    /// Supported drawing attributes are:
462    /// * fgcolor
463    /// * bgcolor
464    /// * bold
465    /// * dim
466    /// * italic
467    /// * underline
468    /// * inverse
469    ///
470    /// This is not typically necessary, since
471    /// [`contents_formatted`](Self::contents_formatted) will leave
472    /// the current active drawing attributes in the correct state, but this
473    /// can be useful in the case of drawing additional things on top of a
474    /// terminal output, since you will need to restore the terminal state
475    /// without the terminal contents necessarily being the same.
476    #[must_use]
477    pub fn attributes_formatted(&self) -> Vec<u8> {
478        let mut contents = vec![];
479        self.write_attributes_formatted(&mut contents);
480        contents
481    }
482
483    fn write_attributes_formatted(&self, contents: &mut Vec<u8>) {
484        crate::term::ClearAttrs.write_buf(contents);
485        self.attrs.write_escape_code_diff(
486            contents,
487            &crate::attrs::Attrs::default(),
488        );
489    }
490
491    /// Returns the current cursor position of the terminal.
492    ///
493    /// The return value will be (row, col).
494    #[must_use]
495    pub fn cursor_position(&self) -> (u16, u16) {
496        let pos = self.grid().pos();
497        (pos.row, pos.col)
498    }
499
500    /// Returns terminal escape sequences sufficient to set the current
501    /// cursor state of the terminal.
502    ///
503    /// This is not typically necessary, since
504    /// [`contents_formatted`](Self::contents_formatted) will leave
505    /// the cursor in the correct state, but this can be useful in the case of
506    /// drawing additional things on top of a terminal output, since you will
507    /// need to restore the terminal state without the terminal contents
508    /// necessarily being the same.
509    ///
510    /// Note that the bytes returned by this function may alter the active
511    /// drawing attributes, because it may require redrawing existing cells in
512    /// order to position the cursor correctly (for instance, in the case
513    /// where the cursor is past the end of a row). Therefore, you should
514    /// ensure to reset the active drawing attributes if necessary after
515    /// processing this data, for instance by using
516    /// [`attributes_formatted`](Self::attributes_formatted).
517    #[must_use]
518    pub fn cursor_state_formatted(&self) -> Vec<u8> {
519        let mut contents = vec![];
520        self.write_cursor_state_formatted(&mut contents);
521        contents
522    }
523
524    fn write_cursor_state_formatted(&self, contents: &mut Vec<u8>) {
525        crate::term::HideCursor::new(self.hide_cursor()).write_buf(contents);
526        self.grid()
527            .write_cursor_position_formatted(contents, None, None);
528
529        // we don't just call write_attributes_formatted here, because that
530        // would still be confusing - consider the case where the user sets
531        // their own unrelated drawing attributes (on a different parser
532        // instance) and then calls cursor_state_formatted. just documenting
533        // it and letting the user handle it on their own is more
534        // straightforward.
535    }
536
537    /// Returns the [`Cell`](crate::Cell) object at the given location in the
538    /// terminal, if it exists.
539    #[must_use]
540    pub fn cell(&self, row: u16, col: u16) -> Option<&crate::Cell> {
541        self.grid().visible_cell(crate::grid::Pos { row, col })
542    }
543
544    /// Returns whether the text in row `row` should wrap to the next line.
545    #[must_use]
546    pub fn row_wrapped(&self, row: u16) -> bool {
547        self.grid()
548            .visible_row(row)
549            .is_some_and(crate::row::Row::wrapped)
550    }
551
552    /// Returns whether the alternate screen is currently in use.
553    #[must_use]
554    pub fn alternate_screen(&self) -> bool {
555        self.mode(MODE_ALTERNATE_SCREEN)
556    }
557
558    /// Returns whether the terminal should be in application keypad mode.
559    #[must_use]
560    pub fn application_keypad(&self) -> bool {
561        self.mode(MODE_APPLICATION_KEYPAD)
562    }
563
564    /// Returns whether the terminal should be in application cursor mode.
565    #[must_use]
566    pub fn application_cursor(&self) -> bool {
567        self.mode(MODE_APPLICATION_CURSOR)
568    }
569
570    /// Returns whether the terminal should be in hide cursor mode.
571    #[must_use]
572    pub fn hide_cursor(&self) -> bool {
573        self.mode(MODE_HIDE_CURSOR)
574    }
575
576    /// Returns whether the terminal should be in bracketed paste mode.
577    #[must_use]
578    pub fn bracketed_paste(&self) -> bool {
579        self.mode(MODE_BRACKETED_PASTE)
580    }
581
582    /// Returns the currently active [`MouseProtocolMode`].
583    #[must_use]
584    pub fn mouse_protocol_mode(&self) -> MouseProtocolMode {
585        self.mouse_protocol_mode
586    }
587
588    /// Returns the currently active [`MouseProtocolEncoding`].
589    #[must_use]
590    pub fn mouse_protocol_encoding(&self) -> MouseProtocolEncoding {
591        self.mouse_protocol_encoding
592    }
593
594    /// Returns the currently active foreground color.
595    #[must_use]
596    pub fn fgcolor(&self) -> crate::Color {
597        self.attrs.fgcolor
598    }
599
600    /// Returns the currently active background color.
601    #[must_use]
602    pub fn bgcolor(&self) -> crate::Color {
603        self.attrs.bgcolor
604    }
605
606    /// Returns whether newly drawn text should be rendered with the bold text
607    /// attribute.
608    #[must_use]
609    pub fn bold(&self) -> bool {
610        self.attrs.bold()
611    }
612
613    /// Returns whether newly drawn text should be rendered with the dim text
614    /// attribute.
615    #[must_use]
616    pub fn dim(&self) -> bool {
617        self.attrs.dim()
618    }
619
620    /// Returns whether newly drawn text should be rendered with the italic
621    /// text attribute.
622    #[must_use]
623    pub fn italic(&self) -> bool {
624        self.attrs.italic()
625    }
626
627    /// Returns whether newly drawn text should be rendered with the
628    /// underlined text attribute.
629    #[must_use]
630    pub fn underline(&self) -> bool {
631        self.attrs.underline()
632    }
633
634    /// Returns whether newly drawn text should be rendered with the inverse
635    /// text attribute.
636    #[must_use]
637    pub fn inverse(&self) -> bool {
638        self.attrs.inverse()
639    }
640
641    pub(crate) fn grid(&self) -> &crate::grid::Grid {
642        if self.mode(MODE_ALTERNATE_SCREEN) {
643            &self.alternate_grid
644        } else {
645            &self.grid
646        }
647    }
648
649    fn grid_mut(&mut self) -> &mut crate::grid::Grid {
650        if self.mode(MODE_ALTERNATE_SCREEN) {
651            &mut self.alternate_grid
652        } else {
653            &mut self.grid
654        }
655    }
656
657    fn enter_alternate_grid(&mut self) {
658        self.grid_mut().set_scrollback(0);
659        self.set_mode(MODE_ALTERNATE_SCREEN);
660        self.alternate_grid.allocate_rows();
661    }
662
663    fn exit_alternate_grid(&mut self) {
664        self.clear_mode(MODE_ALTERNATE_SCREEN);
665    }
666
667    fn save_cursor(&mut self) {
668        self.grid_mut().save_cursor();
669        self.saved_attrs = self.attrs;
670    }
671
672    fn restore_cursor(&mut self) {
673        self.grid_mut().restore_cursor();
674        self.attrs = self.saved_attrs;
675    }
676
677    fn set_mode(&mut self, mode: u8) {
678        self.modes |= mode;
679    }
680
681    fn clear_mode(&mut self, mode: u8) {
682        self.modes &= !mode;
683    }
684
685    fn mode(&self, mode: u8) -> bool {
686        self.modes & mode != 0
687    }
688
689    fn set_mouse_mode(&mut self, mode: MouseProtocolMode) {
690        self.mouse_protocol_mode = mode;
691    }
692
693    fn clear_mouse_mode(&mut self, mode: MouseProtocolMode) {
694        if self.mouse_protocol_mode == mode {
695            self.mouse_protocol_mode = MouseProtocolMode::default();
696        }
697    }
698
699    fn set_mouse_encoding(&mut self, encoding: MouseProtocolEncoding) {
700        self.mouse_protocol_encoding = encoding;
701    }
702
703    fn clear_mouse_encoding(&mut self, encoding: MouseProtocolEncoding) {
704        if self.mouse_protocol_encoding == encoding {
705            self.mouse_protocol_encoding = MouseProtocolEncoding::default();
706        }
707    }
708}
709
710impl Screen {
711    pub(crate) fn text(&mut self, c: char) {
712        let pos = self.grid().pos();
713        let size = self.grid().size();
714        let attrs = self.attrs;
715
716        let width = c.width();
717        if width.is_none() && (u32::from(c)) < 256 {
718            // don't even try to draw control characters
719            return;
720        }
721        let width = width
722            .unwrap_or(1)
723            .try_into()
724            // width() can only return 0, 1, or 2
725            .unwrap();
726
727        // it doesn't make any sense to wrap if the last column in a row
728        // didn't already have contents. don't try to handle the case where a
729        // character wraps because there was only one column left in the
730        // previous row - literally everything handles this case differently,
731        // and this is tmux behavior (and also the simplest). i'm open to
732        // reconsidering this behavior, but only with a really good reason
733        // (xterm handles this by introducing the concept of triple width
734        // cells, which i really don't want to do).
735        let mut wrap = false;
736        if pos.col > size.cols - width {
737            let last_cell = self
738                .grid()
739                .drawing_cell(crate::grid::Pos {
740                    row: pos.row,
741                    col: size.cols - 1,
742                })
743                // pos.row is valid, since it comes directly from
744                // self.grid().pos() which we assume to always have a valid
745                // row value. size.cols - 1 is also always a valid column.
746                .unwrap();
747            if last_cell.has_contents() || last_cell.is_wide_continuation() {
748                wrap = true;
749            }
750        }
751        self.grid_mut().col_wrap(width, wrap);
752        let pos = self.grid().pos();
753
754        if width == 0 {
755            if pos.col > 0 {
756                let mut prev_cell = self
757                    .grid_mut()
758                    .drawing_cell_mut(crate::grid::Pos {
759                        row: pos.row,
760                        col: pos.col - 1,
761                    })
762                    // pos.row is valid, since it comes directly from
763                    // self.grid().pos() which we assume to always have a
764                    // valid row value. pos.col - 1 is valid because we just
765                    // checked for pos.col > 0.
766                    .unwrap();
767                if prev_cell.is_wide_continuation() {
768                    prev_cell = self
769                        .grid_mut()
770                        .drawing_cell_mut(crate::grid::Pos {
771                            row: pos.row,
772                            col: pos.col - 2,
773                        })
774                        // pos.row is valid, since it comes directly from
775                        // self.grid().pos() which we assume to always have a
776                        // valid row value. we know pos.col - 2 is valid
777                        // because the cell at pos.col - 1 is a wide
778                        // continuation character, which means there must be
779                        // the first half of the wide character before it.
780                        .unwrap();
781                }
782                prev_cell.append(c);
783            } else if pos.row > 0 {
784                let prev_row = self
785                    .grid()
786                    .drawing_row(pos.row - 1)
787                    // pos.row is valid, since it comes directly from
788                    // self.grid().pos() which we assume to always have a
789                    // valid row value. pos.row - 1 is valid because we just
790                    // checked for pos.row > 0.
791                    .unwrap();
792                if prev_row.wrapped() {
793                    let mut prev_cell = self
794                        .grid_mut()
795                        .drawing_cell_mut(crate::grid::Pos {
796                            row: pos.row - 1,
797                            col: size.cols - 1,
798                        })
799                        // pos.row is valid, since it comes directly from
800                        // self.grid().pos() which we assume to always have a
801                        // valid row value. pos.row - 1 is valid because we
802                        // just checked for pos.row > 0. col of size.cols - 1
803                        // is always valid.
804                        .unwrap();
805                    if prev_cell.is_wide_continuation() {
806                        prev_cell = self
807                            .grid_mut()
808                            .drawing_cell_mut(crate::grid::Pos {
809                                row: pos.row - 1,
810                                col: size.cols - 2,
811                            })
812                            // pos.row is valid, since it comes directly from
813                            // self.grid().pos() which we assume to always
814                            // have a valid row value. pos.row - 1 is valid
815                            // because we just checked for pos.row > 0. col of
816                            // size.cols - 2 is valid because the cell at
817                            // size.cols - 1 is a wide continuation character,
818                            // so it must have the first half of the wide
819                            // character before it.
820                            .unwrap();
821                    }
822                    prev_cell.append(c);
823                }
824            }
825        } else {
826            if self
827                .grid()
828                .drawing_cell(pos)
829                // pos.row is valid because we assume self.grid().pos() to
830                // always have a valid row value. pos.col is valid because we
831                // called col_wrap() immediately before this, which ensures
832                // that self.grid().pos().col has a valid value.
833                .unwrap()
834                .is_wide_continuation()
835            {
836                let prev_cell = self
837                    .grid_mut()
838                    .drawing_cell_mut(crate::grid::Pos {
839                        row: pos.row,
840                        col: pos.col - 1,
841                    })
842                    // pos.row is valid because we assume self.grid().pos() to
843                    // always have a valid row value. pos.col is valid because
844                    // we called col_wrap() immediately before this, which
845                    // ensures that self.grid().pos().col has a valid value.
846                    // pos.col - 1 is valid because the cell at pos.col is a
847                    // wide continuation character, so it must have the first
848                    // half of the wide character before it.
849                    .unwrap();
850                prev_cell.clear(attrs);
851            }
852
853            if self
854                .grid()
855                .drawing_cell(pos)
856                // pos.row is valid because we assume self.grid().pos() to
857                // always have a valid row value. pos.col is valid because we
858                // called col_wrap() immediately before this, which ensures
859                // that self.grid().pos().col has a valid value.
860                .unwrap()
861                .is_wide()
862            {
863                let next_cell = self
864                    .grid_mut()
865                    .drawing_cell_mut(crate::grid::Pos {
866                        row: pos.row,
867                        col: pos.col + 1,
868                    })
869                    // pos.row is valid because we assume self.grid().pos() to
870                    // always have a valid row value. pos.col is valid because
871                    // we called col_wrap() immediately before this, which
872                    // ensures that self.grid().pos().col has a valid value.
873                    // pos.col + 1 is valid because the cell at pos.col is a
874                    // wide character, so it must have the second half of the
875                    // wide character after it.
876                    .unwrap();
877                next_cell.set(' ', attrs);
878            }
879
880            let cell = self
881                .grid_mut()
882                .drawing_cell_mut(pos)
883                // pos.row is valid because we assume self.grid().pos() to
884                // always have a valid row value. pos.col is valid because we
885                // called col_wrap() immediately before this, which ensures
886                // that self.grid().pos().col has a valid value.
887                .unwrap();
888            cell.set(c, attrs);
889            self.grid_mut().col_inc(1);
890            if width > 1 {
891                let pos = self.grid().pos();
892                if self
893                    .grid()
894                    .drawing_cell(pos)
895                    // pos.row is valid because we assume self.grid().pos() to
896                    // always have a valid row value. pos.col is valid because
897                    // we called col_wrap() earlier, which ensures that
898                    // self.grid().pos().col has a valid value. this is true
899                    // even though we just called col_inc, because this branch
900                    // only happens if width > 1, and col_wrap takes width
901                    // into account.
902                    .unwrap()
903                    .is_wide()
904                {
905                    let next_next_pos = crate::grid::Pos {
906                        row: pos.row,
907                        col: pos.col + 1,
908                    };
909                    let next_next_cell = self
910                        .grid_mut()
911                        .drawing_cell_mut(next_next_pos)
912                        // pos.row is valid because we assume
913                        // self.grid().pos() to always have a valid row value.
914                        // pos.col is valid because we called col_wrap()
915                        // earlier, which ensures that self.grid().pos().col
916                        // has a valid value. this is true even though we just
917                        // called col_inc, because this branch only happens if
918                        // width > 1, and col_wrap takes width into account.
919                        // pos.col + 1 is valid because the cell at pos.col is
920                        // wide, and so it must have the second half of the
921                        // wide character after it.
922                        .unwrap();
923                    next_next_cell.clear(attrs);
924                    if next_next_pos.col == size.cols - 1 {
925                        self.grid_mut()
926                            .drawing_row_mut(pos.row)
927                            // we assume self.grid().pos().row is always valid
928                            .unwrap()
929                            .wrap(false);
930                    }
931                }
932                let next_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
937                    // we called col_wrap() earlier, which ensures that
938                    // self.grid().pos().col has a valid value. this is true
939                    // even though we just called col_inc, because this branch
940                    // only happens if width > 1, and col_wrap takes width
941                    // into account.
942                    .unwrap();
943                next_cell.clear(crate::attrs::Attrs::default());
944                next_cell.set_wide_continuation(true);
945                self.grid_mut().col_inc(1);
946            }
947        }
948    }
949
950    // control codes
951
952    pub(crate) fn bs(&mut self) {
953        self.grid_mut().col_dec(1);
954    }
955
956    pub(crate) fn tab(&mut self) {
957        self.grid_mut().col_tab();
958    }
959
960    pub(crate) fn lf(&mut self) {
961        self.grid_mut().row_inc_scroll(1);
962    }
963
964    pub(crate) fn vt(&mut self) {
965        self.lf();
966    }
967
968    pub(crate) fn ff(&mut self) {
969        self.lf();
970    }
971
972    pub(crate) fn cr(&mut self) {
973        self.grid_mut().col_set(0);
974    }
975
976    // escape codes
977
978    // ESC 7
979    pub(crate) fn decsc(&mut self) {
980        self.save_cursor();
981    }
982
983    // ESC 8
984    pub(crate) fn decrc(&mut self) {
985        self.restore_cursor();
986    }
987
988    // ESC =
989    pub(crate) fn deckpam(&mut self) {
990        self.set_mode(MODE_APPLICATION_KEYPAD);
991    }
992
993    // ESC >
994    pub(crate) fn deckpnm(&mut self) {
995        self.clear_mode(MODE_APPLICATION_KEYPAD);
996    }
997
998    // ESC M
999    pub(crate) fn ri(&mut self) {
1000        self.grid_mut().row_dec_scroll(1);
1001    }
1002
1003    // ESC c
1004    pub(crate) fn ris(&mut self) {
1005        *self = Self::new(self.grid.size(), self.grid.scrollback_len());
1006    }
1007
1008    // csi codes
1009
1010    // CSI @
1011    pub(crate) fn ich(&mut self, count: u16) {
1012        self.grid_mut().insert_cells(count);
1013    }
1014
1015    // CSI A
1016    pub(crate) fn cuu(&mut self, offset: u16) {
1017        self.grid_mut().row_dec_clamp(offset);
1018    }
1019
1020    // CSI B
1021    pub(crate) fn cud(&mut self, offset: u16) {
1022        self.grid_mut().row_inc_clamp(offset);
1023    }
1024
1025    // CSI C
1026    pub(crate) fn cuf(&mut self, offset: u16) {
1027        self.grid_mut().col_inc_clamp(offset);
1028    }
1029
1030    // CSI D
1031    pub(crate) fn cub(&mut self, offset: u16) {
1032        self.grid_mut().col_dec(offset);
1033    }
1034
1035    // CSI E
1036    pub(crate) fn cnl(&mut self, offset: u16) {
1037        self.grid_mut().col_set(0);
1038        self.grid_mut().row_inc_clamp(offset);
1039    }
1040
1041    // CSI F
1042    pub(crate) fn cpl(&mut self, offset: u16) {
1043        self.grid_mut().col_set(0);
1044        self.grid_mut().row_dec_clamp(offset);
1045    }
1046
1047    // CSI G
1048    pub(crate) fn cha(&mut self, col: u16) {
1049        self.grid_mut().col_set(col - 1);
1050    }
1051
1052    // CSI H
1053    pub(crate) fn cup(&mut self, (row, col): (u16, u16)) {
1054        self.grid_mut().set_pos(crate::grid::Pos {
1055            row: row - 1,
1056            col: col - 1,
1057        });
1058    }
1059
1060    // CSI J
1061    pub(crate) fn ed(
1062        &mut self,
1063        mode: u16,
1064        mut unhandled: impl FnMut(&mut Self),
1065    ) {
1066        let attrs = self.attrs;
1067        match mode {
1068            0 => self.grid_mut().erase_all_forward(attrs),
1069            1 => self.grid_mut().erase_all_backward(attrs),
1070            2 => self.grid_mut().erase_all(attrs),
1071            _ => unhandled(self),
1072        }
1073    }
1074
1075    // CSI ? J
1076    pub(crate) fn decsed(
1077        &mut self,
1078        mode: u16,
1079        unhandled: impl FnMut(&mut Self),
1080    ) {
1081        self.ed(mode, unhandled);
1082    }
1083
1084    // CSI K
1085    pub(crate) fn el(
1086        &mut self,
1087        mode: u16,
1088        mut unhandled: impl FnMut(&mut Self),
1089    ) {
1090        let attrs = self.attrs;
1091        match mode {
1092            0 => self.grid_mut().erase_row_forward(attrs),
1093            1 => self.grid_mut().erase_row_backward(attrs),
1094            2 => self.grid_mut().erase_row(attrs),
1095            _ => unhandled(self),
1096        }
1097    }
1098
1099    // CSI ? K
1100    pub(crate) fn decsel(
1101        &mut self,
1102        mode: u16,
1103        unhandled: impl FnMut(&mut Self),
1104    ) {
1105        self.el(mode, unhandled);
1106    }
1107
1108    // CSI L
1109    pub(crate) fn il(&mut self, count: u16) {
1110        self.grid_mut().insert_lines(count);
1111    }
1112
1113    // CSI M
1114    pub(crate) fn dl(&mut self, count: u16) {
1115        self.grid_mut().delete_lines(count);
1116    }
1117
1118    // CSI P
1119    pub(crate) fn dch(&mut self, count: u16) {
1120        self.grid_mut().delete_cells(count);
1121    }
1122
1123    // CSI S
1124    pub(crate) fn su(&mut self, count: u16) {
1125        self.grid_mut().scroll_up(count);
1126    }
1127
1128    // CSI T
1129    pub(crate) fn sd(&mut self, count: u16) {
1130        self.grid_mut().scroll_down(count);
1131    }
1132
1133    // CSI X
1134    pub(crate) fn ech(&mut self, count: u16) {
1135        let attrs = self.attrs;
1136        self.grid_mut().erase_cells(count, attrs);
1137    }
1138
1139    // CSI d
1140    pub(crate) fn vpa(&mut self, row: u16) {
1141        self.grid_mut().row_set(row - 1);
1142    }
1143
1144    // CSI ? h
1145    pub(crate) fn decset(
1146        &mut self,
1147        params: &vte::Params,
1148        mut unhandled: impl FnMut(&mut Self),
1149    ) {
1150        for param in params {
1151            match param {
1152                [1] => self.set_mode(MODE_APPLICATION_CURSOR),
1153                [6] => self.grid_mut().set_origin_mode(true),
1154                [9] => self.set_mouse_mode(MouseProtocolMode::Press),
1155                [25] => self.clear_mode(MODE_HIDE_CURSOR),
1156                [47] => self.enter_alternate_grid(),
1157                [1000] => {
1158                    self.set_mouse_mode(MouseProtocolMode::PressRelease);
1159                }
1160                [1002] => {
1161                    self.set_mouse_mode(MouseProtocolMode::ButtonMotion);
1162                }
1163                [1003] => self.set_mouse_mode(MouseProtocolMode::AnyMotion),
1164                [1005] => {
1165                    self.set_mouse_encoding(MouseProtocolEncoding::Utf8);
1166                }
1167                [1006] => {
1168                    self.set_mouse_encoding(MouseProtocolEncoding::Sgr);
1169                }
1170                [1049] => {
1171                    self.decsc();
1172                    self.alternate_grid.clear();
1173                    self.enter_alternate_grid();
1174                }
1175                [2004] => self.set_mode(MODE_BRACKETED_PASTE),
1176                _ => unhandled(self),
1177            }
1178        }
1179    }
1180
1181    // CSI ? l
1182    pub(crate) fn decrst(
1183        &mut self,
1184        params: &vte::Params,
1185        mut unhandled: impl FnMut(&mut Self),
1186    ) {
1187        for param in params {
1188            match param {
1189                [1] => self.clear_mode(MODE_APPLICATION_CURSOR),
1190                [6] => self.grid_mut().set_origin_mode(false),
1191                [9] => self.clear_mouse_mode(MouseProtocolMode::Press),
1192                [25] => self.set_mode(MODE_HIDE_CURSOR),
1193                [47] => {
1194                    self.exit_alternate_grid();
1195                }
1196                [1000] => {
1197                    self.clear_mouse_mode(MouseProtocolMode::PressRelease);
1198                }
1199                [1002] => {
1200                    self.clear_mouse_mode(MouseProtocolMode::ButtonMotion);
1201                }
1202                [1003] => {
1203                    self.clear_mouse_mode(MouseProtocolMode::AnyMotion);
1204                }
1205                [1005] => {
1206                    self.clear_mouse_encoding(MouseProtocolEncoding::Utf8);
1207                }
1208                [1006] => {
1209                    self.clear_mouse_encoding(MouseProtocolEncoding::Sgr);
1210                }
1211                [1049] => {
1212                    self.exit_alternate_grid();
1213                    self.decrc();
1214                }
1215                [2004] => self.clear_mode(MODE_BRACKETED_PASTE),
1216                _ => unhandled(self),
1217            }
1218        }
1219    }
1220
1221    // CSI m
1222    pub(crate) fn sgr(
1223        &mut self,
1224        params: &vte::Params,
1225        mut unhandled: impl FnMut(&mut Self),
1226    ) {
1227        // XXX really i want to just be able to pass in a default Params
1228        // instance with a 0 in it, but vte doesn't allow creating new Params
1229        // instances
1230        if params.is_empty() {
1231            self.attrs = crate::attrs::Attrs::default();
1232            return;
1233        }
1234
1235        let mut iter = params.iter();
1236
1237        macro_rules! next_param {
1238            () => {
1239                match iter.next() {
1240                    Some(n) => n,
1241                    _ => return,
1242                }
1243            };
1244        }
1245
1246        macro_rules! to_u8 {
1247            ($n:expr) => {
1248                if let Some(n) = u16_to_u8($n) {
1249                    n
1250                } else {
1251                    return;
1252                }
1253            };
1254        }
1255
1256        macro_rules! next_param_u8 {
1257            () => {
1258                if let &[n] = next_param!() {
1259                    to_u8!(n)
1260                } else {
1261                    return;
1262                }
1263            };
1264        }
1265
1266        loop {
1267            match next_param!() {
1268                [0] => self.attrs = crate::attrs::Attrs::default(),
1269                [1] => self.attrs.set_bold(),
1270                [2] => self.attrs.set_dim(),
1271                [3] => self.attrs.set_italic(true),
1272                [4] => self.attrs.set_underline(true),
1273                [7] => self.attrs.set_inverse(true),
1274                [22] => self.attrs.set_normal_intensity(),
1275                [23] => self.attrs.set_italic(false),
1276                [24] => self.attrs.set_underline(false),
1277                [27] => self.attrs.set_inverse(false),
1278                [n] if (30..=37).contains(n) => {
1279                    self.attrs.fgcolor = crate::Color::Idx(to_u8!(*n) - 30);
1280                }
1281                [38, 2, r, g, b] => {
1282                    self.attrs.fgcolor =
1283                        crate::Color::Rgb(to_u8!(*r), to_u8!(*g), to_u8!(*b));
1284                }
1285                [38, 5, i] => {
1286                    self.attrs.fgcolor = crate::Color::Idx(to_u8!(*i));
1287                }
1288                [38] => match next_param!() {
1289                    [2] => {
1290                        let r = next_param_u8!();
1291                        let g = next_param_u8!();
1292                        let b = next_param_u8!();
1293                        self.attrs.fgcolor = crate::Color::Rgb(r, g, b);
1294                    }
1295                    [5] => {
1296                        self.attrs.fgcolor =
1297                            crate::Color::Idx(next_param_u8!());
1298                    }
1299                    _ => {
1300                        unhandled(self);
1301                        return;
1302                    }
1303                },
1304                [39] => {
1305                    self.attrs.fgcolor = crate::Color::Default;
1306                }
1307                [n] if (40..=47).contains(n) => {
1308                    self.attrs.bgcolor = crate::Color::Idx(to_u8!(*n) - 40);
1309                }
1310                [48, 2, r, g, b] => {
1311                    self.attrs.bgcolor =
1312                        crate::Color::Rgb(to_u8!(*r), to_u8!(*g), to_u8!(*b));
1313                }
1314                [48, 5, i] => {
1315                    self.attrs.bgcolor = crate::Color::Idx(to_u8!(*i));
1316                }
1317                [48] => match next_param!() {
1318                    [2] => {
1319                        let r = next_param_u8!();
1320                        let g = next_param_u8!();
1321                        let b = next_param_u8!();
1322                        self.attrs.bgcolor = crate::Color::Rgb(r, g, b);
1323                    }
1324                    [5] => {
1325                        self.attrs.bgcolor =
1326                            crate::Color::Idx(next_param_u8!());
1327                    }
1328                    _ => {
1329                        unhandled(self);
1330                        return;
1331                    }
1332                },
1333                [49] => {
1334                    self.attrs.bgcolor = crate::Color::Default;
1335                }
1336                [n] if (90..=97).contains(n) => {
1337                    self.attrs.fgcolor = crate::Color::Idx(to_u8!(*n) - 82);
1338                }
1339                [n] if (100..=107).contains(n) => {
1340                    self.attrs.bgcolor = crate::Color::Idx(to_u8!(*n) - 92);
1341                }
1342                _ => unhandled(self),
1343            }
1344        }
1345    }
1346
1347    // CSI r
1348    pub(crate) fn decstbm(&mut self, (top, bottom): (u16, u16)) {
1349        self.grid_mut().set_scroll_region(top - 1, bottom - 1);
1350    }
1351}
1352
1353fn u16_to_u8(i: u16) -> Option<u8> {
1354    if i > u16::from(u8::MAX) {
1355        None
1356    } else {
1357        // safe because we just ensured that the value fits in a u8
1358        Some(i.try_into().unwrap())
1359    }
1360}