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    let fg = screen_cell.fgcolor();
41    let bg = screen_cell.bgcolor();
42    if screen_cell.has_contents() {
43        buf_cell.set_symbol(screen_cell.contents());
44    }
45    let fg: Color = fg.into();
46    let bg: Color = bg.into();
47    let mut style = Style::reset();
48    if screen_cell.bold() {
49        style = style.add_modifier(Modifier::BOLD);
50    }
51    if screen_cell.italic() {
52        style = style.add_modifier(Modifier::ITALIC);
53    }
54    if screen_cell.underline() {
55        style = style.add_modifier(Modifier::UNDERLINED);
56    }
57    if screen_cell.inverse() {
58        style = style.add_modifier(Modifier::REVERSED);
59    }
60    if screen_cell.dim() {
61        style = style.add_modifier(Modifier::DIM);
62    }
63    buf_cell.set_style(style.fg(fg.into()).bg(bg.into()));
64}
65
66/// Represents a foreground or background color for cells.
67/// Intermediate translation layer between
68/// [`vt100::Screen`] and [`ratatui_core::style::Color`]
69#[allow(dead_code)]
70enum Color {
71    Reset,
72    Black,
73    Red,
74    Green,
75    Yellow,
76    Blue,
77    Magenta,
78    Cyan,
79    Gray,
80    DarkGray,
81    LightRed,
82    LightGreen,
83    LightYellow,
84    LightBlue,
85    LightMagenta,
86    LightCyan,
87    White,
88    Rgb(u8, u8, u8),
89    Indexed(u8),
90}
91
92impl From<vt100::Color> for Color {
93    #[inline]
94    fn from(value: vt100::Color) -> Self {
95        match value {
96            vt100::Color::Default => Self::Reset,
97            vt100::Color::Idx(i) => Self::Indexed(i),
98            vt100::Color::Rgb(r, g, b) => Self::Rgb(r, g, b),
99        }
100    }
101}
102
103impl From<Color> for vt100::Color {
104    #[inline]
105    fn from(value: Color) -> Self {
106        match value {
107            Color::Reset => Self::Default,
108            Color::Black => Self::Idx(0),
109            Color::Red => Self::Idx(1),
110            Color::Green => Self::Idx(2),
111            Color::Yellow => Self::Idx(3),
112            Color::Blue => Self::Idx(4),
113            Color::Magenta => Self::Idx(5),
114            Color::Cyan => Self::Idx(6),
115            Color::Gray => Self::Idx(7),
116            Color::DarkGray => Self::Idx(8),
117            Color::LightRed => Self::Idx(9),
118            Color::LightGreen => Self::Idx(10),
119            Color::LightYellow => Self::Idx(11),
120            Color::LightBlue => Self::Idx(12),
121            Color::LightMagenta => Self::Idx(13),
122            Color::LightCyan => Self::Idx(14),
123            Color::White => Self::Idx(15),
124            Color::Rgb(r, g, b) => Self::Rgb(r, g, b),
125            Color::Indexed(i) => Self::Idx(i),
126        }
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use crate::widget::Screen;
133
134    #[test]
135    fn cursor_position_offset_by_scrollback() {
136        let mut parser = vt100::Parser::new(6, 80, 20);
137        for i in 0..9 {
138            parser.process(format!("line {i}\r\n").as_bytes());
139        }
140        let (drawing_row, drawing_col) = parser.screen().cursor_position();
141
142        parser.screen_mut().set_scrollback(2);
143        let visible_pos = Screen::cursor_position(parser.screen());
144        assert_eq!(visible_pos, (drawing_row + 2, drawing_col));
145    }
146
147    #[test]
148    fn cursor_position_offset_preserves_col() {
149        let mut parser = vt100::Parser::new(6, 80, 20);
150        for i in 0..9 {
151            parser.process(format!("line {i}\r\n").as_bytes());
152        }
153        parser.process(b"partial");
154        let (drawing_row, drawing_col) = parser.screen().cursor_position();
155        assert_eq!(drawing_col, 7);
156
157        parser.screen_mut().set_scrollback(2);
158        let (visible_row, visible_col) = Screen::cursor_position(parser.screen());
159        assert_eq!(visible_row, drawing_row + 2);
160        assert_eq!(visible_col, 7, "scrollback should not affect column");
161    }
162
163    #[test]
164    fn cursor_position_off_screen_with_full_scrollback() {
165        let mut parser = vt100::Parser::new(4, 80, 20);
166        for i in 0..10 {
167            parser.process(format!("line {i}\r\n").as_bytes());
168        }
169
170        parser.screen_mut().set_scrollback(4);
171        let (visible_row, _) = Screen::cursor_position(parser.screen());
172        assert!(
173            visible_row >= 4,
174            "cursor at visible row {visible_row} should be off-screen (>= 4 rows)"
175        );
176    }
177}
178
179impl From<Color> for ratatui_core::style::Color {
180    #[inline]
181    fn from(value: Color) -> Self {
182        match value {
183            Color::Reset => Self::Reset,
184            Color::Black => Self::Black,
185            Color::Red => Self::Red,
186            Color::Green => Self::Green,
187            Color::Yellow => Self::Yellow,
188            Color::Blue => Self::Blue,
189            Color::Magenta => Self::Magenta,
190            Color::Cyan => Self::Cyan,
191            Color::Gray => Self::Gray,
192            Color::DarkGray => Self::DarkGray,
193            Color::LightRed => Self::LightRed,
194            Color::LightGreen => Self::LightGreen,
195            Color::LightYellow => Self::LightYellow,
196            Color::LightBlue => Self::LightBlue,
197            Color::LightMagenta => Self::LightMagenta,
198            Color::LightCyan => Self::LightCyan,
199            Color::White => Self::White,
200            Color::Rgb(r, g, b) => Self::Rgb(r, g, b),
201            Color::Indexed(i) => Self::Indexed(i),
202        }
203    }
204}