Skip to main content

vt100_psmux/
screen.rs

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