Skip to main content

tui_term/
vt100_imp.rs

1use ratatui_core::style::{Modifier, Style};
2
3use crate::widget::{Cell, Screen};
4
5impl Screen for vt100::Screen {
6    type C = vt100::Cell;
7
8    #[inline]
9    fn cell(&self, row: u16, col: u16) -> Option<&Self::C> {
10        self.cell(row, col)
11    }
12
13    #[inline]
14    fn hide_cursor(&self) -> bool {
15        self.hide_cursor()
16    }
17
18    #[inline]
19    fn cursor_position(&self) -> (u16, u16) {
20        let (row, col) = self.cursor_position();
21        let scrollback = u16::try_from(self.scrollback()).unwrap_or(u16::MAX);
22        (row.saturating_add(scrollback), col)
23    }
24}
25
26impl Cell for vt100::Cell {
27    #[inline]
28    fn has_contents(&self) -> bool {
29        self.has_contents()
30    }
31
32    #[inline]
33    fn apply(&self, cell: &mut ratatui_core::buffer::Cell) {
34        fill_buf_cell(self, cell)
35    }
36}
37
38#[inline]
39fn fill_buf_cell(screen_cell: &vt100::Cell, buf_cell: &mut ratatui_core::buffer::Cell) {
40    if screen_cell.has_contents() {
41        buf_cell.set_symbol(screen_cell.contents());
42    }
43
44    let mut modifier = Modifier::empty();
45    if screen_cell.bold() {
46        modifier |= Modifier::BOLD;
47    }
48    if screen_cell.italic() {
49        modifier |= Modifier::ITALIC;
50    }
51    if screen_cell.underline() {
52        modifier |= Modifier::UNDERLINED;
53    }
54    if screen_cell.inverse() {
55        modifier |= Modifier::REVERSED;
56    }
57    if screen_cell.dim() {
58        modifier |= Modifier::DIM;
59    }
60
61    let fg = map_color(screen_cell.fgcolor());
62    let bg = map_color(screen_cell.bgcolor());
63
64    buf_cell.set_style(Style::reset().fg(fg).bg(bg).add_modifier(modifier));
65}
66
67#[inline]
68fn map_color(color: vt100::Color) -> ratatui_core::style::Color {
69    match color {
70        vt100::Color::Default => ratatui_core::style::Color::Reset,
71        vt100::Color::Idx(i) => ratatui_core::style::Color::Indexed(i),
72        vt100::Color::Rgb(r, g, b) => ratatui_core::style::Color::Rgb(r, g, b),
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use crate::widget::Screen;
79
80    #[test]
81    fn cursor_position_offset_by_scrollback() {
82        let mut parser = vt100::Parser::new(6, 80, 20);
83        for i in 0..9 {
84            parser.process(format!("line {i}\r\n").as_bytes());
85        }
86        let (drawing_row, drawing_col) = parser.screen().cursor_position();
87
88        parser.screen_mut().set_scrollback(2);
89        let visible_pos = Screen::cursor_position(parser.screen());
90        assert_eq!(visible_pos, (drawing_row + 2, drawing_col));
91    }
92
93    #[test]
94    fn cursor_position_offset_preserves_col() {
95        let mut parser = vt100::Parser::new(6, 80, 20);
96        for i in 0..9 {
97            parser.process(format!("line {i}\r\n").as_bytes());
98        }
99        parser.process(b"partial");
100        let (drawing_row, drawing_col) = parser.screen().cursor_position();
101        assert_eq!(drawing_col, 7);
102
103        parser.screen_mut().set_scrollback(2);
104        let (visible_row, visible_col) = Screen::cursor_position(parser.screen());
105        assert_eq!(visible_row, drawing_row + 2);
106        assert_eq!(visible_col, 7, "scrollback should not affect column");
107    }
108
109    #[test]
110    fn cursor_position_off_screen_with_full_scrollback() {
111        let mut parser = vt100::Parser::new(4, 80, 20);
112        for i in 0..10 {
113            parser.process(format!("line {i}\r\n").as_bytes());
114        }
115
116        parser.screen_mut().set_scrollback(4);
117        let (visible_row, _) = Screen::cursor_position(parser.screen());
118        assert!(
119            visible_row >= 4,
120            "cursor at visible row {visible_row} should be off-screen (>= 4 rows)"
121        );
122    }
123}