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#[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}