Skip to main content

tui_term/
widget.rs

1use std::borrow::Cow;
2
3use ratatui_core::{
4    buffer::Buffer,
5    layout::Rect,
6    style::{Color, Modifier, Style},
7    widgets::Widget,
8};
9use ratatui_widgets::{block::Block, clear::Clear};
10
11use crate::state;
12
13/// A trait representing a pseudo-terminal screen.
14///
15/// Implementing this trait allows for backends other than `vt100` to be used
16/// with the `PseudoTerminal` widget.
17///
18/// All row coordinates use a visible coordinate system where row 0 is the
19/// topmost row currently displayed. When scrollback is active, this may
20/// differ from the underlying terminal buffer's row numbering. Implementors
21/// must ensure that [`cell`](Self::cell) and [`cursor_position`](Self::cursor_position)
22/// use the same coordinate space.
23pub trait Screen {
24    /// The type of cell this screen contains
25    type C: Cell;
26
27    /// Returns the cell at the given location in visible coordinates.
28    ///
29    /// Row 0 is the topmost visible row, accounting for any scrollback offset.
30    fn cell(&self, row: u16, col: u16) -> Option<&Self::C>;
31    /// Returns whether the cursor should be hidden.
32    fn hide_cursor(&self) -> bool;
33    /// Returns the cursor position in visible coordinates.
34    ///
35    /// The return value is (row, column), where row 0 is the topmost visible
36    /// row. This must use the same coordinate space as [`cell`](Self::cell).
37    /// When scrollback is active, the cursor row must be shifted down by the
38    /// number of scrollback rows visible above the active screen content.
39    ///
40    /// If the cursor is not within the visible area (e.g., scrolled
41    /// off-screen), the returned row may exceed the screen dimensions.
42    /// The rendering layer handles bounds checking.
43    fn cursor_position(&self) -> (u16, u16);
44
45    /// Returns the preferred shape of the cursor.
46    ///
47    /// The default implementation returns [`CursorShape::Default`], which
48    /// preserves the widget's configured cursor symbol. Backends that can
49    /// report the terminal's preferred cursor shape should override this.
50    fn cursor_shape(&self) -> CursorShape {
51        CursorShape::Default
52    }
53}
54
55/// A trait for representing a single cell on a screen.
56pub trait Cell {
57    /// Whether the cell has any contents that could be rendered to the screen.
58    fn has_contents(&self) -> bool;
59    /// Apply the contents and styling of this cell to the provided buffer cell.
60    fn apply(&self, cell: &mut ratatui_core::buffer::Cell);
61}
62
63/// Represents the shape of the cursor following DECSCUSR (CSI Ps SP q).
64#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
65#[non_exhaustive]
66pub enum CursorShape {
67    /// Fall back to the widget-configured cursor symbol (DECSCUSR 0, terminal default).
68    #[default]
69    Default,
70    /// A blinking block cursor (DECSCUSR 1).
71    BlinkingBlock,
72    /// A steady block cursor (DECSCUSR 2).
73    SteadyBlock,
74    /// A blinking underline cursor (DECSCUSR 3).
75    BlinkingUnderline,
76    /// A steady underline cursor (DECSCUSR 4).
77    SteadyUnderline,
78    /// A blinking bar cursor (DECSCUSR 5).
79    BlinkingBar,
80    /// A steady bar cursor (DECSCUSR 6).
81    SteadyBar,
82}
83
84/// A widget representing a pseudo-terminal screen.
85///
86/// The `PseudoTerminal` widget displays the contents of a pseudo-terminal screen,
87/// which is typically populated with text and control sequences from a terminal emulator.
88/// It provides a visual representation of the terminal output within a TUI application.
89///
90/// The contents of the pseudo-terminal screen are represented by a `vt100::Screen` object.
91/// The `vt100` library provides functionality for parsing and processing terminal control sequences
92/// and handling terminal state, allowing the `PseudoTerminal` widget to accurately render the
93/// terminal output.
94///
95/// # Examples
96///
97/// ```rust
98/// use ratatui_core::style::{Color, Modifier, Style};
99/// use ratatui_widgets::{block::Block, borders::Borders};
100/// use tui_term::widget::PseudoTerminal;
101/// use vt100::Parser;
102///
103/// let mut parser = vt100::Parser::new(24, 80, 0);
104/// let pseudo_term = PseudoTerminal::new(parser.screen())
105///     .block(Block::default().title("Terminal").borders(Borders::ALL))
106///     .style(
107///         Style::default()
108///             .fg(Color::White)
109///             .bg(Color::Black)
110///             .add_modifier(Modifier::BOLD),
111///     );
112/// ```
113#[non_exhaustive]
114pub struct PseudoTerminal<'a, S> {
115    screen: &'a S,
116    pub(crate) block: Option<Block<'a>>,
117    style: Option<Style>,
118    pub(crate) cursor: Cursor,
119}
120
121#[non_exhaustive]
122pub struct Cursor {
123    pub(crate) show: bool,
124    pub(crate) symbol: Cow<'static, str>,
125    pub(crate) style: Style,
126    pub(crate) overlay_style: Style,
127}
128
129impl Cursor {
130    /// Sets the symbol for the cursor.
131    ///
132    /// # Arguments
133    ///
134    /// * `symbol`: The symbol to set as the cursor.
135    ///
136    /// # Example
137    ///
138    /// ```
139    /// use ratatui_core::style::Style;
140    /// use tui_term::widget::Cursor;
141    ///
142    /// let cursor = Cursor::default().symbol("|");
143    /// ```
144    #[inline]
145    #[must_use]
146    pub fn symbol(mut self, symbol: impl Into<Cow<'static, str>>) -> Self {
147        self.symbol = symbol.into();
148        self
149    }
150
151    /// Sets the style for the cursor.
152    ///
153    /// # Arguments
154    ///
155    /// * `style`: The `Style` to set for the cursor.
156    ///
157    /// # Example
158    ///
159    /// ```
160    /// use ratatui_core::style::Style;
161    /// use tui_term::widget::Cursor;
162    ///
163    /// let cursor = Cursor::default().style(Style::default());
164    /// ```
165    #[inline]
166    #[must_use]
167    pub const fn style(mut self, style: Style) -> Self {
168        self.style = style;
169        self
170    }
171
172    /// Sets the overlay style for the cursor.
173    ///
174    /// The overlay style is used when the cursor overlaps with existing content on the screen.
175    ///
176    /// # Arguments
177    ///
178    /// * `overlay_style`: The `Style` to set as the overlay style for the cursor.
179    ///
180    /// # Example
181    ///
182    /// ```
183    /// use ratatui_core::style::Style;
184    /// use tui_term::widget::Cursor;
185    ///
186    /// let cursor = Cursor::default().overlay_style(Style::default());
187    /// ```
188    #[inline]
189    #[must_use]
190    pub const fn overlay_style(mut self, overlay_style: Style) -> Self {
191        self.overlay_style = overlay_style;
192        self
193    }
194
195    /// Set the visibility of the cursor (default = shown)
196    #[inline]
197    #[must_use]
198    pub const fn visibility(mut self, show: bool) -> Self {
199        self.show = show;
200        self
201    }
202
203    /// Show the cursor (default)
204    #[inline]
205    pub fn show(&mut self) {
206        self.show = true;
207    }
208
209    /// Hide the cursor
210    #[inline]
211    pub fn hide(&mut self) {
212        self.show = false;
213    }
214}
215
216impl Default for Cursor {
217    #[inline]
218    fn default() -> Self {
219        Self {
220            show: true,
221            symbol: Cow::Borrowed("\u{2588}"), //"█".
222            style: Style::default().fg(Color::Gray),
223            overlay_style: Style::default().add_modifier(Modifier::REVERSED),
224        }
225    }
226}
227
228impl<'a, S: Screen> PseudoTerminal<'a, S> {
229    /// Creates a new instance of `PseudoTerminal`.
230    ///
231    /// # Arguments
232    ///
233    /// * `screen`: The reference to the `Screen`.
234    ///
235    /// # Example
236    ///
237    /// ```
238    /// use tui_term::widget::PseudoTerminal;
239    /// use vt100::Parser;
240    ///
241    /// let mut parser = vt100::Parser::new(24, 80, 0);
242    /// let pseudo_term = PseudoTerminal::new(parser.screen());
243    /// ```
244    #[inline]
245    #[must_use]
246    pub fn new(screen: &'a S) -> Self {
247        PseudoTerminal {
248            screen,
249            block: None,
250            style: None,
251            cursor: Cursor::default(),
252        }
253    }
254
255    /// Sets the block for the `PseudoTerminal`.
256    ///
257    /// # Arguments
258    ///
259    /// * `block`: The `Block` to set.
260    ///
261    /// # Example
262    ///
263    /// ```
264    /// use ratatui_widgets::block::Block;
265    /// use tui_term::widget::PseudoTerminal;
266    /// use vt100::Parser;
267    ///
268    /// let mut parser = vt100::Parser::new(24, 80, 0);
269    /// let block = Block::default();
270    /// let pseudo_term = PseudoTerminal::new(parser.screen()).block(block);
271    /// ```
272    #[inline]
273    #[must_use]
274    pub fn block(mut self, block: Block<'a>) -> Self {
275        self.block = Some(block);
276        self
277    }
278
279    /// Sets the cursor configuration for the `PseudoTerminal`.
280    ///
281    /// The `cursor` method allows configuring the appearance of the cursor within the
282    /// `PseudoTerminal` widget.
283    ///
284    /// # Arguments
285    ///
286    /// * `cursor`: The `Cursor` configuration to set.
287    ///
288    /// # Example
289    ///
290    /// ```rust
291    /// use ratatui_core::style::Style;
292    /// use tui_term::widget::{Cursor, PseudoTerminal};
293    ///
294    /// let mut parser = vt100::Parser::new(24, 80, 0);
295    /// let cursor = Cursor::default().symbol("|").style(Style::default());
296    /// let pseudo_term = PseudoTerminal::new(parser.screen()).cursor(cursor);
297    /// ```
298    #[inline]
299    #[must_use]
300    pub fn cursor(mut self, cursor: Cursor) -> Self {
301        self.cursor = cursor;
302        self
303    }
304
305    /// Sets the style for `PseudoTerminal`.
306    ///
307    /// # Arguments
308    ///
309    /// * `style`: The `Style` to set.
310    ///
311    /// # Example
312    ///
313    /// ```
314    /// use ratatui_core::style::Style;
315    /// use tui_term::widget::PseudoTerminal;
316    ///
317    /// let mut parser = vt100::Parser::new(24, 80, 0);
318    /// let style = Style::default();
319    /// let pseudo_term = PseudoTerminal::new(parser.screen()).style(style);
320    /// ```
321    #[inline]
322    #[must_use]
323    pub const fn style(mut self, style: Style) -> Self {
324        self.style = Some(style);
325        self
326    }
327
328    #[inline]
329    #[must_use]
330    pub const fn screen(&self) -> &S {
331        self.screen
332    }
333}
334
335impl<S: Screen> Widget for &PseudoTerminal<'_, S> {
336    #[inline]
337    fn render(self, area: Rect, buf: &mut Buffer) {
338        Clear.render(area, buf);
339        let area = self.block.as_ref().map_or(area, |b| {
340            let inner_area = b.inner(area);
341            b.clone().render(area, buf);
342            inner_area
343        });
344        state::handle(self, area, buf);
345    }
346}
347
348impl<S: Screen> Widget for PseudoTerminal<'_, S> {
349    #[inline]
350    fn render(self, area: Rect, buf: &mut Buffer) {
351        (&self).render(area, buf);
352    }
353}
354
355#[cfg(all(test, feature = "vt100"))]
356mod tests {
357    use ratatui::Terminal;
358    use ratatui_core::backend::TestBackend;
359    use ratatui_widgets::borders::Borders;
360
361    use super::*;
362
363    fn snapshot_typescript(stream: &[u8]) -> String {
364        let backend = TestBackend::new(80, 24);
365        let mut terminal = Terminal::new(backend).unwrap();
366        let mut parser = vt100::Parser::new(24, 80, 0);
367        parser.process(stream);
368        let pseudo_term = PseudoTerminal::new(parser.screen());
369        terminal
370            .draw(|f| {
371                f.render_widget(pseudo_term, f.area());
372            })
373            .unwrap();
374        format!("{:?}", terminal.backend().buffer())
375    }
376
377    #[test]
378    fn scrollback_cursor_not_rendered_when_off_screen() {
379        let backend = TestBackend::new(40, 4);
380        let mut terminal = Terminal::new(backend).unwrap();
381        let mut parser = vt100::Parser::new(4, 40, 20);
382
383        // 10 lines on 4 rows: 6 scroll off. With set_scrollback(4) all
384        // visible rows are scrollback. Cursor maps to visible row 7, off-screen.
385        for i in 0..10 {
386            parser.process(format!("[scrollback line {i}]\r\n").as_bytes());
387        }
388        parser.screen_mut().set_scrollback(4);
389
390        let pseudo_term = PseudoTerminal::new(parser.screen());
391        terminal
392            .draw(|f| {
393                f.render_widget(pseudo_term, f.area());
394            })
395            .unwrap();
396
397        let buf = terminal.backend().buffer();
398        for row in 0..4 {
399            for col in 0..40 {
400                let cell = &buf[(col, row)];
401                assert!(
402                    !cell.modifier.contains(Modifier::REVERSED),
403                    "REVERSED cursor style should not appear at ({col}, {row}) \
404                     when cursor is off-screen due to scrollback"
405                );
406            }
407        }
408        let view = format!("{:?}", terminal.backend().buffer());
409        insta::assert_snapshot!(view);
410    }
411
412    #[test]
413    fn scrollback_partial_cursor_visible_at_adjusted_row() {
414        let backend = TestBackend::new(40, 6);
415        let mut terminal = Terminal::new(backend).unwrap();
416        let mut parser = vt100::Parser::new(6, 40, 20);
417
418        // 8 lines on 6 rows: 3 scroll off into scrollback.
419        // \x1b[H moves cursor to drawing row 0.
420        parser.process(b"[scrollback: hidden 1]\r\n");
421        parser.process(b"[scrollback: hidden 2]\r\n");
422        parser.process(b"[scrollback: shown at row 0]\r\n");
423        parser.process(b"[draw-0: cursor row]\r\n");
424        parser.process(b"[draw-1]\r\n");
425        parser.process(b"[draw-2]\r\n");
426        parser.process(b"[draw-3]\r\n");
427        parser.process(b"[draw-4]\r\n");
428        parser.process(b"\x1b[H");
429
430        // With scrollback=1, drawing row 0 shifts to visible row 1.
431        // Visible row 0 becomes the last scrollback line.
432        parser.screen_mut().set_scrollback(1);
433
434        let pseudo_term = PseudoTerminal::new(parser.screen());
435        terminal
436            .draw(|f| {
437                f.render_widget(pseudo_term, f.area());
438            })
439            .unwrap();
440
441        let buf = terminal.backend().buffer();
442        for col in 0..40 {
443            let cell = &buf[(col, 0)];
444            assert!(
445                !cell.modifier.contains(Modifier::REVERSED),
446                "scrollback row 0 at col {col} should not have cursor styling"
447            );
448        }
449        let cursor_cell = &buf[(0, 1)];
450        assert!(
451            cursor_cell.modifier.contains(Modifier::REVERSED),
452            "cursor should render at visible row 1 (adjusted from drawing row 0)"
453        );
454        let view = format!("{:?}", terminal.backend().buffer());
455        insta::assert_snapshot!(view);
456    }
457
458    #[test]
459    fn scrollback_hide_cursor_suppresses_rendering() {
460        let backend = TestBackend::new(40, 6);
461        let mut terminal = Terminal::new(backend).unwrap();
462        let mut parser = vt100::Parser::new(6, 40, 20);
463
464        for i in 0..8 {
465            parser.process(format!("line {i}\r\n").as_bytes());
466        }
467        parser.process(b"\x1b[H");
468        // Hide cursor via escape sequence
469        parser.process(b"\x1b[?25l");
470        parser.screen_mut().set_scrollback(1);
471
472        let pseudo_term = PseudoTerminal::new(parser.screen());
473        terminal
474            .draw(|f| {
475                f.render_widget(pseudo_term, f.area());
476            })
477            .unwrap();
478
479        let buf = terminal.backend().buffer();
480        for row in 0..6 {
481            for col in 0..40 {
482                let cell = &buf[(col, row)];
483                assert!(
484                    !cell.modifier.contains(Modifier::REVERSED),
485                    "hidden cursor should not style ({col}, {row}) even with scrollback"
486                );
487            }
488        }
489    }
490
491    #[test]
492    fn scrollback_cursor_with_block() {
493        let backend = TestBackend::new(42, 8);
494        let mut terminal = Terminal::new(backend).unwrap();
495        let mut parser = vt100::Parser::new(6, 40, 20);
496
497        for i in 0..8 {
498            parser.process(format!("line {i}\r\n").as_bytes());
499        }
500        parser.process(b"\x1b[H");
501        parser.screen_mut().set_scrollback(1);
502
503        let block = Block::default().borders(Borders::ALL).title("pty");
504        let pseudo_term = PseudoTerminal::new(parser.screen()).block(block);
505        terminal
506            .draw(|f| {
507                f.render_widget(pseudo_term, f.area());
508            })
509            .unwrap();
510
511        // Border takes 1 row/col on each side, so inner area starts at (1,1).
512        // Visible row 0 is scrollback (inner buf row 1), no cursor.
513        // Cursor at drawing row 0 + scrollback 1 = visible row 1 (inner buf row 2).
514        let buf = terminal.backend().buffer();
515        let scrollback_cell = &buf[(1, 1)];
516        assert!(
517            !scrollback_cell.modifier.contains(Modifier::REVERSED),
518            "scrollback row inside block should not have cursor styling"
519        );
520        let cursor_cell = &buf[(1, 2)];
521        assert!(
522            cursor_cell.modifier.contains(Modifier::REVERSED),
523            "cursor should render at inner row 1 (buf row 2) with block border"
524        );
525    }
526
527    #[test]
528    fn empty_actions() {
529        let backend = TestBackend::new(80, 24);
530        let mut terminal = Terminal::new(backend).unwrap();
531        let mut parser = vt100::Parser::new(24, 80, 0);
532        parser.process(b" ");
533        let pseudo_term = PseudoTerminal::new(parser.screen());
534        terminal
535            .draw(|f| {
536                f.render_widget(pseudo_term, f.area());
537            })
538            .unwrap();
539        let view = format!("{:?}", terminal.backend().buffer());
540        insta::assert_snapshot!(view);
541    }
542    #[test]
543    fn boundary_rows_overshot_no_panic() {
544        let stream = include_bytes!("../test/typescript/simple_ls.typescript");
545        // Make the backend on purpose much smaller
546        let backend = TestBackend::new(80, 4);
547        let mut terminal = Terminal::new(backend).unwrap();
548        let mut parser = vt100::Parser::new(24, 80, 0);
549        parser.process(stream);
550        let pseudo_term = PseudoTerminal::new(parser.screen());
551        terminal
552            .draw(|f| {
553                f.render_widget(pseudo_term, f.area());
554            })
555            .unwrap();
556        let view = format!("{:?}", terminal.backend().buffer());
557        insta::assert_snapshot!(view);
558    }
559
560    struct MockShapeScreen {
561        shape: CursorShape,
562        c_row: u16,
563        c_col: u16,
564    }
565    struct MockCell;
566    impl Cell for MockCell {
567        fn has_contents(&self) -> bool {
568            false
569        }
570        fn apply(&self, _cell: &mut ratatui_core::buffer::Cell) {}
571    }
572    impl Screen for MockShapeScreen {
573        type C = MockCell;
574        fn cell(&self, _row: u16, _col: u16) -> Option<&Self::C> {
575            Some(&MockCell)
576        }
577        fn hide_cursor(&self) -> bool {
578            false
579        }
580        fn cursor_position(&self) -> (u16, u16) {
581            (self.c_row, self.c_col)
582        }
583        fn cursor_shape(&self) -> CursorShape {
584            self.shape
585        }
586    }
587
588    fn assert_cursor_shape(shape: CursorShape, expected_symbol: &str) {
589        let backend = TestBackend::new(5, 5);
590        let mut terminal = Terminal::new(backend).unwrap();
591        let screen = MockShapeScreen {
592            shape,
593            c_row: 1,
594            c_col: 1,
595        };
596        let pseudo_term = PseudoTerminal::new(&screen);
597        terminal
598            .draw(|f| {
599                f.render_widget(pseudo_term, f.area());
600            })
601            .unwrap();
602        let buf = terminal.backend().buffer();
603        assert_eq!(buf[(1, 1)].symbol(), expected_symbol);
604    }
605
606    #[test]
607    fn cursor_shape_block() {
608        assert_cursor_shape(CursorShape::BlinkingBlock, "█");
609    }
610
611    #[test]
612    fn cursor_shape_underline() {
613        assert_cursor_shape(CursorShape::BlinkingUnderline, "▁");
614    }
615
616    #[test]
617    fn cursor_shape_bar() {
618        assert_cursor_shape(CursorShape::BlinkingBar, "▏");
619    }
620
621    #[test]
622    fn simple_ls() {
623        let stream = include_bytes!("../test/typescript/simple_ls.typescript");
624        let view = snapshot_typescript(stream);
625        insta::assert_snapshot!(view);
626    }
627    #[test]
628    fn simple_cursor_alternate_symbol() {
629        let stream = include_bytes!("../test/typescript/simple_ls.typescript");
630        let backend = TestBackend::new(80, 24);
631        let mut terminal = Terminal::new(backend).unwrap();
632        let mut parser = vt100::Parser::new(24, 80, 0);
633        let cursor = Cursor::default().symbol("|");
634        parser.process(stream);
635        let pseudo_term = PseudoTerminal::new(parser.screen()).cursor(cursor);
636        terminal
637            .draw(|f| {
638                f.render_widget(pseudo_term, f.area());
639            })
640            .unwrap();
641        let view = format!("{:?}", terminal.backend().buffer());
642        insta::assert_snapshot!(view);
643    }
644    #[test]
645    fn simple_cursor_styled() {
646        let stream = include_bytes!("../test/typescript/simple_ls.typescript");
647        let backend = TestBackend::new(80, 24);
648        let mut terminal = Terminal::new(backend).unwrap();
649        let mut parser = vt100::Parser::new(24, 80, 0);
650        let style = Style::default().bg(Color::Cyan).fg(Color::LightRed);
651        let cursor = Cursor::default().symbol("|").style(style);
652        parser.process(stream);
653        let pseudo_term = PseudoTerminal::new(parser.screen()).cursor(cursor);
654        terminal
655            .draw(|f| {
656                f.render_widget(pseudo_term, f.area());
657            })
658            .unwrap();
659        let view = format!("{:?}", terminal.backend().buffer());
660        insta::assert_snapshot!(view);
661    }
662    #[test]
663    fn simple_cursor_hide() {
664        let stream = include_bytes!("../test/typescript/simple_ls.typescript");
665        let backend = TestBackend::new(80, 24);
666        let mut terminal = Terminal::new(backend).unwrap();
667        let mut parser = vt100::Parser::new(24, 80, 0);
668        let cursor = Cursor::default().visibility(false);
669        parser.process(stream);
670        let pseudo_term = PseudoTerminal::new(parser.screen()).cursor(cursor);
671        terminal
672            .draw(|f| {
673                f.render_widget(pseudo_term, f.area());
674            })
675            .unwrap();
676        let view = format!("{:?}", terminal.backend().buffer());
677        insta::assert_snapshot!(view);
678    }
679    #[test]
680    fn simple_cursor_hide_alt() {
681        let stream = include_bytes!("../test/typescript/simple_ls.typescript");
682        let backend = TestBackend::new(80, 24);
683        let mut terminal = Terminal::new(backend).unwrap();
684        let mut parser = vt100::Parser::new(24, 80, 0);
685        let mut cursor = Cursor::default();
686        cursor.hide();
687        parser.process(stream);
688        let pseudo_term = PseudoTerminal::new(parser.screen()).cursor(cursor);
689        terminal
690            .draw(|f| {
691                f.render_widget(pseudo_term, f.area());
692            })
693            .unwrap();
694        let view = format!("{:?}", terminal.backend().buffer());
695        insta::assert_snapshot!(view);
696    }
697    #[test]
698    fn overlapping_cursor() {
699        let stream = include_bytes!("../test/typescript/overlapping_cursor.typescript");
700        let view = snapshot_typescript(stream);
701        insta::assert_snapshot!(view);
702    }
703    #[test]
704    fn overlapping_cursor_alternate_style() {
705        let stream = include_bytes!("../test/typescript/overlapping_cursor.typescript");
706        let backend = TestBackend::new(80, 24);
707        let mut terminal = Terminal::new(backend).unwrap();
708        let mut parser = vt100::Parser::new(24, 80, 0);
709        let style = Style::default().bg(Color::Cyan).fg(Color::LightRed);
710        let cursor = Cursor::default().overlay_style(style);
711        parser.process(stream);
712        let pseudo_term = PseudoTerminal::new(parser.screen()).cursor(cursor);
713        terminal
714            .draw(|f| {
715                f.render_widget(pseudo_term, f.area());
716            })
717            .unwrap();
718        let view = format!("{:?}", terminal.backend().buffer());
719        insta::assert_snapshot!(view);
720    }
721    #[test]
722    fn simple_ls_with_block() {
723        let stream = include_bytes!("../test/typescript/simple_ls.typescript");
724        let backend = TestBackend::new(100, 24);
725        let mut terminal = Terminal::new(backend).unwrap();
726        let mut parser = vt100::Parser::new(24, 80, 0);
727        parser.process(stream);
728        let block = Block::default().borders(Borders::ALL).title("ls");
729        let pseudo_term = PseudoTerminal::new(parser.screen()).block(block);
730        terminal
731            .draw(|f| {
732                f.render_widget(pseudo_term, f.area());
733            })
734            .unwrap();
735        let view = format!("{:?}", terminal.backend().buffer());
736        insta::assert_snapshot!(view);
737    }
738    #[test]
739    fn simple_ls_no_style_from_block() {
740        let stream = include_bytes!("../test/typescript/simple_ls.typescript");
741        let backend = TestBackend::new(100, 24);
742        let mut terminal = Terminal::new(backend).unwrap();
743        let mut parser = vt100::Parser::new(24, 80, 0);
744        parser.process(stream);
745        let block = Block::default()
746            .borders(Borders::ALL)
747            .style(Style::default().add_modifier(Modifier::BOLD))
748            .title("ls");
749        let pseudo_term = PseudoTerminal::new(parser.screen()).block(block);
750        terminal
751            .draw(|f| {
752                f.render_widget(pseudo_term, f.area());
753            })
754            .unwrap();
755        let view = format!("{:?}", terminal.backend().buffer());
756        insta::assert_snapshot!(view);
757    }
758    #[test]
759    fn italic_text() {
760        let stream = b"This line will be displayed in italic. This should have no style.";
761        let view = snapshot_typescript(stream);
762        insta::assert_snapshot!(view);
763    }
764    #[test]
765    fn underlined_text() {
766        let stream =
767            b"This line will be displayed with an underline. This should have no style.";
768        let view = snapshot_typescript(stream);
769        insta::assert_snapshot!(view);
770    }
771    #[test]
772    fn bold_text() {
773        let stream = b"This line will be displayed bold. This should have no style.";
774        let view = snapshot_typescript(stream);
775        insta::assert_snapshot!(view);
776    }
777    #[test]
778    fn inverse_text() {
779        let stream = b"This line will be displayed inversed. This should have no style.";
780        let view = snapshot_typescript(stream);
781        insta::assert_snapshot!(view);
782    }
783    #[test]
784    fn dim_text() {
785        let stream =
786            b"\x1b[2mThis line will be displayed dim/faint.\x1b[0m This should have no style.";
787        let view = snapshot_typescript(stream);
788        insta::assert_snapshot!(view);
789    }
790    #[test]
791    fn combined_modifier_text() {
792        let stream =
793            b"This line will be displayed in italic and underlined. This should have no style.";
794        let view = snapshot_typescript(stream);
795        insta::assert_snapshot!(view);
796    }
797    #[test]
798    fn dim_bold_text() {
799        let stream = b"\x1b[2m\x1b[1mThis is dim and bold. Bold takes precedence.\x1b[0m Normal.";
800        let view = snapshot_typescript(stream);
801        insta::assert_snapshot!(view);
802    }
803
804    #[test]
805    fn vttest_02_01() {
806        let stream = include_bytes!("../test/typescript/vttest_02_01.typescript");
807        let view = snapshot_typescript(stream);
808        insta::assert_snapshot!(view);
809    }
810    #[test]
811    fn vttest_02_02() {
812        let stream = include_bytes!("../test/typescript/vttest_02_02.typescript");
813        let view = snapshot_typescript(stream);
814        insta::assert_snapshot!(view);
815    }
816    #[test]
817    fn vttest_02_03() {
818        let stream = include_bytes!("../test/typescript/vttest_02_03.typescript");
819        let view = snapshot_typescript(stream);
820        insta::assert_snapshot!(view);
821    }
822    #[test]
823    fn vttest_02_04() {
824        let stream = include_bytes!("../test/typescript/vttest_02_04.typescript");
825        let view = snapshot_typescript(stream);
826        insta::assert_snapshot!(view);
827    }
828    #[test]
829    fn vttest_02_05() {
830        let stream = include_bytes!("../test/typescript/vttest_02_05.typescript");
831        let view = snapshot_typescript(stream);
832        insta::assert_snapshot!(view);
833    }
834    #[test]
835    fn vttest_02_06() {
836        let stream = include_bytes!("../test/typescript/vttest_02_06.typescript");
837        let view = snapshot_typescript(stream);
838        insta::assert_snapshot!(view);
839    }
840    #[test]
841    fn vttest_02_07() {
842        let stream = include_bytes!("../test/typescript/vttest_02_07.typescript");
843        let view = snapshot_typescript(stream);
844        insta::assert_snapshot!(view);
845    }
846    #[test]
847    fn vttest_02_08() {
848        let stream = include_bytes!("../test/typescript/vttest_02_08.typescript");
849        let view = snapshot_typescript(stream);
850        insta::assert_snapshot!(view);
851    }
852    #[test]
853    fn vttest_02_09() {
854        let stream = include_bytes!("../test/typescript/vttest_02_09.typescript");
855        let view = snapshot_typescript(stream);
856        insta::assert_snapshot!(view);
857    }
858    #[test]
859    fn vttest_02_10() {
860        let stream = include_bytes!("../test/typescript/vttest_02_10.typescript");
861        let view = snapshot_typescript(stream);
862        insta::assert_snapshot!(view);
863    }
864    #[test]
865    fn vttest_02_11() {
866        let stream = include_bytes!("../test/typescript/vttest_02_11.typescript");
867        let view = snapshot_typescript(stream);
868        insta::assert_snapshot!(view);
869    }
870    #[test]
871    fn vttest_02_12() {
872        let stream = include_bytes!("../test/typescript/vttest_02_12.typescript");
873        let view = snapshot_typescript(stream);
874        insta::assert_snapshot!(view);
875    }
876    #[test]
877    fn vttest_02_13() {
878        let stream = include_bytes!("../test/typescript/vttest_02_13.typescript");
879        let view = snapshot_typescript(stream);
880        insta::assert_snapshot!(view);
881    }
882    #[test]
883    fn vttest_02_14() {
884        let stream = include_bytes!("../test/typescript/vttest_02_14.typescript");
885        let view = snapshot_typescript(stream);
886        insta::assert_snapshot!(view);
887    }
888    #[test]
889    fn vttest_02_15() {
890        let stream = include_bytes!("../test/typescript/vttest_02_15.typescript");
891        let view = snapshot_typescript(stream);
892        insta::assert_snapshot!(view);
893    }
894
895    #[test]
896    fn vttest_03_01() {
897        let stream = include_bytes!("../test/typescript/vttest_03_01.typescript");
898        let view = snapshot_typescript(stream);
899        insta::assert_snapshot!(view);
900    }
901
902    #[test]
903    fn vttest_01_01() {
904        let stream = include_bytes!("../test/typescript/vttest_01_01.typescript");
905        let view = snapshot_typescript(stream);
906        insta::assert_snapshot!(view);
907    }
908
909    #[test]
910    fn vttest_01_02() {
911        let stream = include_bytes!("../test/typescript/vttest_01_02.typescript");
912        let view = snapshot_typescript(stream);
913        insta::assert_snapshot!(view);
914    }
915
916    #[test]
917    fn vttest_01_03() {
918        let stream = include_bytes!("../test/typescript/vttest_01_03.typescript");
919        let view = snapshot_typescript(stream);
920        insta::assert_snapshot!(view);
921    }
922
923    #[test]
924    fn vttest_01_04() {
925        let stream = include_bytes!("../test/typescript/vttest_01_04.typescript");
926        let view = snapshot_typescript(stream);
927        insta::assert_snapshot!(view);
928    }
929
930    #[test]
931    fn vttest_01_05() {
932        let stream = include_bytes!("../test/typescript/vttest_01_05.typescript");
933        let view = snapshot_typescript(stream);
934        insta::assert_snapshot!(view);
935    }
936
937    #[test]
938    fn vttest_menu() {
939        let stream = include_bytes!("../test/typescript/vttest_menu.typescript");
940        let view = snapshot_typescript(stream);
941        insta::assert_snapshot!(view);
942    }
943
944    #[test]
945    fn vttest_08_01() {
946        let stream = include_bytes!("../test/typescript/vttest_08_01.typescript");
947        let view = snapshot_typescript(stream);
948        insta::assert_snapshot!(view);
949    }
950
951    #[test]
952    fn vttest_08_02() {
953        let stream = include_bytes!("../test/typescript/vttest_08_02.typescript");
954        let view = snapshot_typescript(stream);
955        insta::assert_snapshot!(view);
956    }
957
958    #[test]
959    fn vttest_08_03() {
960        let stream = include_bytes!("../test/typescript/vttest_08_03.typescript");
961        let view = snapshot_typescript(stream);
962        insta::assert_snapshot!(view);
963    }
964
965    #[test]
966    fn vttest_08_04() {
967        let stream = include_bytes!("../test/typescript/vttest_08_04.typescript");
968        let view = snapshot_typescript(stream);
969        insta::assert_snapshot!(view);
970    }
971
972    #[test]
973    fn vttest_08_05() {
974        let stream = include_bytes!("../test/typescript/vttest_08_05.typescript");
975        let view = snapshot_typescript(stream);
976        insta::assert_snapshot!(view);
977    }
978
979    #[test]
980    fn vttest_08_06() {
981        let stream = include_bytes!("../test/typescript/vttest_08_06.typescript");
982        let view = snapshot_typescript(stream);
983        insta::assert_snapshot!(view);
984    }
985
986    #[test]
987    fn vttest_08_07() {
988        let stream = include_bytes!("../test/typescript/vttest_08_07.typescript");
989        let view = snapshot_typescript(stream);
990        insta::assert_snapshot!(view);
991    }
992
993    #[test]
994    fn vttest_08_08() {
995        let stream = include_bytes!("../test/typescript/vttest_08_08.typescript");
996        let view = snapshot_typescript(stream);
997        insta::assert_snapshot!(view);
998    }
999
1000    #[test]
1001    fn vttest_08_09() {
1002        let stream = include_bytes!("../test/typescript/vttest_08_09.typescript");
1003        let view = snapshot_typescript(stream);
1004        insta::assert_snapshot!(view);
1005    }
1006
1007    #[test]
1008    fn vttest_08_10() {
1009        let stream = include_bytes!("../test/typescript/vttest_08_10.typescript");
1010        let view = snapshot_typescript(stream);
1011        insta::assert_snapshot!(view);
1012    }
1013
1014    #[test]
1015    fn vttest_08_11() {
1016        let stream = include_bytes!("../test/typescript/vttest_08_11.typescript");
1017        let view = snapshot_typescript(stream);
1018        insta::assert_snapshot!(view);
1019    }
1020
1021    #[test]
1022    fn vttest_08_12() {
1023        let stream = include_bytes!("../test/typescript/vttest_08_12.typescript");
1024        let view = snapshot_typescript(stream);
1025        insta::assert_snapshot!(view);
1026    }
1027
1028    #[test]
1029    fn vttest_08_13() {
1030        let stream = include_bytes!("../test/typescript/vttest_08_13.typescript");
1031        let view = snapshot_typescript(stream);
1032        insta::assert_snapshot!(view);
1033    }
1034
1035    #[test]
1036    fn vttest_08_14() {
1037        let stream = include_bytes!("../test/typescript/vttest_08_14.typescript");
1038        let view = snapshot_typescript(stream);
1039        insta::assert_snapshot!(view);
1040    }
1041
1042    #[test]
1043    fn vttest_11_06_02() {
1044        let stream = include_bytes!("../test/typescript/vttest_11_06_02.typescript");
1045        let view = snapshot_typescript(stream);
1046        insta::assert_snapshot!(view);
1047    }
1048
1049    #[test]
1050    fn vttest_11_06_03() {
1051        let stream = include_bytes!("../test/typescript/vttest_11_06_03.typescript");
1052        let view = snapshot_typescript(stream);
1053        insta::assert_snapshot!(view);
1054    }
1055
1056    #[test]
1057    fn vttest_11_06_04() {
1058        let stream = include_bytes!("../test/typescript/vttest_11_06_04.typescript");
1059        let view = snapshot_typescript(stream);
1060        insta::assert_snapshot!(view);
1061    }
1062
1063    #[test]
1064    fn vttest_11_06_05() {
1065        let stream = include_bytes!("../test/typescript/vttest_11_06_05.typescript");
1066        let view = snapshot_typescript(stream);
1067        insta::assert_snapshot!(view);
1068    }
1069
1070    #[test]
1071    fn vttest_09_01() {
1072        let stream = include_bytes!("../test/typescript/vttest_09_01.typescript");
1073        let view = snapshot_typescript(stream);
1074        insta::assert_snapshot!(view);
1075    }
1076
1077    #[test]
1078    fn vttest_09_02() {
1079        let stream = include_bytes!("../test/typescript/vttest_09_02.typescript");
1080        let view = snapshot_typescript(stream);
1081        insta::assert_snapshot!(view);
1082    }
1083
1084    #[test]
1085    fn vttest_09_03() {
1086        let stream = include_bytes!("../test/typescript/vttest_09_03.typescript");
1087        let view = snapshot_typescript(stream);
1088        insta::assert_snapshot!(view);
1089    }
1090
1091    #[test]
1092    fn vttest_09_04() {
1093        let stream = include_bytes!("../test/typescript/vttest_09_04.typescript");
1094        let view = snapshot_typescript(stream);
1095        insta::assert_snapshot!(view);
1096    }
1097
1098    #[test]
1099    fn vttest_09_05() {
1100        let stream = include_bytes!("../test/typescript/vttest_09_05.typescript");
1101        let view = snapshot_typescript(stream);
1102        insta::assert_snapshot!(view);
1103    }
1104
1105    #[test]
1106    fn vttest_09_06() {
1107        let stream = include_bytes!("../test/typescript/vttest_09_06.typescript");
1108        let view = snapshot_typescript(stream);
1109        insta::assert_snapshot!(view);
1110    }
1111
1112    #[test]
1113    fn vttest_09_07() {
1114        let stream = include_bytes!("../test/typescript/vttest_09_07.typescript");
1115        let view = snapshot_typescript(stream);
1116        insta::assert_snapshot!(view);
1117    }
1118
1119    #[test]
1120    fn vttest_09_08() {
1121        let stream = include_bytes!("../test/typescript/vttest_09_08.typescript");
1122        let view = snapshot_typescript(stream);
1123        insta::assert_snapshot!(view);
1124    }
1125
1126    #[test]
1127    fn vttest_09_09() {
1128        let stream = include_bytes!("../test/typescript/vttest_09_09.typescript");
1129        let view = snapshot_typescript(stream);
1130        insta::assert_snapshot!(view);
1131    }
1132
1133    #[test]
1134    fn vttest_11_05_01() {
1135        let stream = include_bytes!("../test/typescript/vttest_11_05_01.typescript");
1136        let view = snapshot_typescript(stream);
1137        insta::assert_snapshot!(view);
1138    }
1139
1140    #[test]
1141    fn vttest_11_05_02() {
1142        let stream = include_bytes!("../test/typescript/vttest_11_05_02.typescript");
1143        let view = snapshot_typescript(stream);
1144        insta::assert_snapshot!(view);
1145    }
1146
1147    #[test]
1148    fn vttest_11_05_03() {
1149        let stream = include_bytes!("../test/typescript/vttest_11_05_03.typescript");
1150        let view = snapshot_typescript(stream);
1151        insta::assert_snapshot!(view);
1152    }
1153
1154    #[test]
1155    fn vttest_11_05_04() {
1156        let stream = include_bytes!("../test/typescript/vttest_11_05_04.typescript");
1157        let view = snapshot_typescript(stream);
1158        insta::assert_snapshot!(view);
1159    }
1160
1161    #[test]
1162    fn vttest_11_05_05() {
1163        let stream = include_bytes!("../test/typescript/vttest_11_05_05.typescript");
1164        let view = snapshot_typescript(stream);
1165        insta::assert_snapshot!(view);
1166    }
1167
1168    #[test]
1169    fn vttest_11_05_06() {
1170        let stream = include_bytes!("../test/typescript/vttest_11_05_06.typescript");
1171        let view = snapshot_typescript(stream);
1172        insta::assert_snapshot!(view);
1173    }
1174
1175    #[test]
1176    fn vttest_11_05_07() {
1177        let stream = include_bytes!("../test/typescript/vttest_11_05_07.typescript");
1178        let view = snapshot_typescript(stream);
1179        insta::assert_snapshot!(view);
1180    }
1181
1182    #[test]
1183    fn vttest_11_05_08() {
1184        let stream = include_bytes!("../test/typescript/vttest_11_05_08.typescript");
1185        let view = snapshot_typescript(stream);
1186        insta::assert_snapshot!(view);
1187    }
1188
1189    #[test]
1190    fn vttest_11_05_09() {
1191        let stream = include_bytes!("../test/typescript/vttest_11_05_09.typescript");
1192        let view = snapshot_typescript(stream);
1193        insta::assert_snapshot!(view);
1194    }
1195
1196    #[test]
1197    fn vttest_11_07_02() {
1198        let stream = include_bytes!("../test/typescript/vttest_11_07_02.typescript");
1199        let view = snapshot_typescript(stream);
1200        insta::assert_snapshot!(view);
1201    }
1202}