1use crate::sync_utils::mutex_lock_or_recover;
2use std::sync::{Arc, Mutex};
3use vt100::Parser;
4
5#[derive(Debug, Clone, Default)]
6pub struct CellStyle {
7 pub bold: bool,
8 pub underline: bool,
9 pub inverse: bool,
10 pub fg_color: Option<Color>,
11 pub bg_color: Option<Color>,
12}
13
14#[derive(Debug, Clone)]
15pub enum Color {
16 Default,
17 Indexed(u8),
18 Rgb(u8, u8, u8),
19}
20
21#[derive(Debug, Clone)]
22pub struct Cell {
23 pub char: char,
24 pub style: CellStyle,
25}
26
27#[derive(Debug, Clone)]
28pub struct ScreenBuffer {
29 pub cells: Vec<Vec<Cell>>,
30}
31
32#[derive(Debug, Clone)]
33pub struct CursorPosition {
34 pub row: u16,
35 pub col: u16,
36 pub visible: bool,
37}
38
39pub struct VirtualTerminal {
40 parser: Arc<Mutex<Parser>>,
41 cols: u16,
42 rows: u16,
43}
44
45impl VirtualTerminal {
46 pub fn new(cols: u16, rows: u16) -> Self {
47 let parser = Parser::new(rows, cols, 0);
48 Self {
49 parser: Arc::new(Mutex::new(parser)),
50 cols,
51 rows,
52 }
53 }
54
55 pub fn process(&self, data: &[u8]) {
56 let mut parser = mutex_lock_or_recover(&self.parser);
57 parser.process(data);
58 }
59
60 pub fn screen_text(&self) -> String {
61 let parser = mutex_lock_or_recover(&self.parser);
62 let screen = parser.screen();
63
64 let mut lines = Vec::new();
65 for row in 0..screen.size().0 {
66 let mut line = String::new();
67 for col in 0..screen.size().1 {
68 let cell = screen.cell(row, col);
69 if let Some(cell) = cell {
70 line.push(cell.contents().chars().next().unwrap_or(' '));
71 } else {
72 line.push(' ');
73 }
74 }
75
76 let trimmed = line.trim_end();
77 lines.push(trimmed.to_string());
78 }
79
80 while lines.last().map(|l| l.is_empty()).unwrap_or(false) {
81 lines.pop();
82 }
83
84 lines.join("\n")
85 }
86
87 pub fn screen_buffer(&self) -> ScreenBuffer {
88 let parser = mutex_lock_or_recover(&self.parser);
89 let screen = parser.screen();
90
91 let mut cells = Vec::new();
92 for row in 0..screen.size().0 {
93 let mut row_cells = Vec::new();
94 for col in 0..screen.size().1 {
95 let cell = screen.cell(row, col);
96 let (char, style) = if let Some(cell) = cell {
97 let c = cell.contents().chars().next().unwrap_or(' ');
98 let s = CellStyle {
99 bold: cell.bold(),
100 underline: cell.underline(),
101 inverse: cell.inverse(),
102 fg_color: convert_color(cell.fgcolor()),
103 bg_color: convert_color(cell.bgcolor()),
104 };
105 (c, s)
106 } else {
107 (' ', CellStyle::default())
108 };
109 row_cells.push(Cell { char, style });
110 }
111 cells.push(row_cells);
112 }
113
114 ScreenBuffer { cells }
115 }
116
117 pub fn cursor(&self) -> CursorPosition {
118 let parser = mutex_lock_or_recover(&self.parser);
119 let screen = parser.screen();
120 let (row, col) = screen.cursor_position();
121
122 CursorPosition {
123 row,
124 col,
125 visible: !screen.hide_cursor(),
126 }
127 }
128
129 pub fn resize(&mut self, cols: u16, rows: u16) {
130 let mut parser = mutex_lock_or_recover(&self.parser);
131 parser.set_size(rows, cols);
132 self.cols = cols;
133 self.rows = rows;
134 }
135
136 pub fn size(&self) -> (u16, u16) {
137 (self.cols, self.rows)
138 }
139
140 pub fn clear(&mut self) {
141 let rows = self.rows;
142 let cols = self.cols;
143 let mut parser = mutex_lock_or_recover(&self.parser);
144 parser.set_size(rows, cols);
145 }
146}
147
148fn convert_color(color: vt100::Color) -> Option<Color> {
149 match color {
150 vt100::Color::Default => Some(Color::Default),
151 vt100::Color::Idx(idx) => Some(Color::Indexed(idx)),
152 vt100::Color::Rgb(r, g, b) => Some(Color::Rgb(r, g, b)),
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_basic_terminal() {
162 let term = VirtualTerminal::new(80, 24);
163 term.process(b"Hello, World!");
164 let text = term.screen_text();
165 assert!(text.contains("Hello, World!"));
166 }
167
168 #[test]
169 fn test_cursor_position() {
170 let term = VirtualTerminal::new(80, 24);
171 term.process(b"ABC");
172 let cursor = term.cursor();
173 assert_eq!(cursor.col, 3);
174 assert_eq!(cursor.row, 0);
175 }
176
177 #[test]
178 fn test_screen_buffer() {
179 let term = VirtualTerminal::new(80, 24);
180 term.process(b"\x1b[1mBold\x1b[0m Normal");
181 let buffer = term.screen_buffer();
182
183 assert!(buffer.cells[0][0].style.bold);
184 assert_eq!(buffer.cells[0][0].char, 'B');
185 }
186}