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}