1use std::fmt::{Debug, Formatter, Write as _};
2use std::io::Write as _;
3use std::sync::{Arc, Mutex};
4
5use vt100::Parser;
6
7use crate::TermLike;
8
9#[cfg_attr(docsrs, doc(cfg(feature = "in_memory")))]
13#[derive(Debug, Clone)]
14pub struct InMemoryTerm {
15 state: Arc<Mutex<InMemoryTermState>>,
16}
17
18impl InMemoryTerm {
19 pub fn new(rows: u16, cols: u16) -> InMemoryTerm {
20 assert!(rows > 0, "rows must be > 0");
21 assert!(cols > 0, "cols must be > 0");
22 InMemoryTerm {
23 state: Arc::new(Mutex::new(InMemoryTermState::new(rows, cols))),
24 }
25 }
26
27 pub fn reset(&self) {
28 let mut state = self.state.lock().unwrap();
29 *state = InMemoryTermState::new(state.height, state.width);
30 }
31
32 pub fn contents(&self) -> String {
33 let state = self.state.lock().unwrap();
34
35 let mut rows = state
39 .parser
40 .screen()
41 .rows(0, state.width)
42 .collect::<Vec<_>>();
43
44 rows = rows
46 .into_iter()
47 .rev()
48 .skip_while(|line| line.is_empty())
49 .map(|line| line.trim_end().to_string())
50 .collect();
51
52 rows.reverse();
54 rows.join("\n")
55 }
56
57 pub fn contents_formatted(&self) -> Vec<u8> {
58 let state = self.state.lock().unwrap();
59
60 let mut rows = state
64 .parser
65 .screen()
66 .rows_formatted(0, state.width)
67 .collect::<Vec<_>>();
68
69 rows = rows
71 .into_iter()
72 .rev()
73 .skip_while(|line| line.is_empty())
74 .collect();
75
76 rows.reverse();
78
79 let reset = b"[m";
81 let len = rows.iter().map(|line| line.len() + reset.len() + 1).sum();
82
83 let mut contents = rows.iter().fold(Vec::with_capacity(len), |mut acc, cur| {
85 acc.extend_from_slice(cur);
86 acc.extend_from_slice(reset);
87 acc.push(b'\n');
88 acc
89 });
90
91 contents.truncate(len.saturating_sub(1));
93 contents
94 }
95
96 pub fn moves_since_last_check(&self) -> String {
97 let mut s = String::new();
98 for line in std::mem::take(&mut self.state.lock().unwrap().history) {
99 writeln!(s, "{line:?}").unwrap();
100 }
101 s
102 }
103}
104
105impl TermLike for InMemoryTerm {
106 fn width(&self) -> u16 {
107 self.state.lock().unwrap().width
108 }
109
110 fn height(&self) -> u16 {
111 self.state.lock().unwrap().height
112 }
113
114 fn move_cursor_up(&self, n: usize) -> std::io::Result<()> {
115 match n {
116 0 => Ok(()),
117 _ => {
118 let mut state = self.state.lock().unwrap();
119 state.history.push(Move::Up(n));
120 state.write_str(&format!("\x1b[{n}A"))
121 }
122 }
123 }
124
125 fn move_cursor_down(&self, n: usize) -> std::io::Result<()> {
126 match n {
127 0 => Ok(()),
128 _ => {
129 let mut state = self.state.lock().unwrap();
130 state.history.push(Move::Down(n));
131 state.write_str(&format!("\x1b[{n}B"))
132 }
133 }
134 }
135
136 fn move_cursor_right(&self, n: usize) -> std::io::Result<()> {
137 match n {
138 0 => Ok(()),
139 _ => {
140 let mut state = self.state.lock().unwrap();
141 state.history.push(Move::Right(n));
142 state.write_str(&format!("\x1b[{n}C"))
143 }
144 }
145 }
146
147 fn move_cursor_left(&self, n: usize) -> std::io::Result<()> {
148 match n {
149 0 => Ok(()),
150 _ => {
151 let mut state = self.state.lock().unwrap();
152 state.history.push(Move::Left(n));
153 state.write_str(&format!("\x1b[{n}D"))
154 }
155 }
156 }
157
158 fn write_line(&self, s: &str) -> std::io::Result<()> {
159 let mut state = self.state.lock().unwrap();
160 state.history.push(Move::Str(s.into()));
161 state.history.push(Move::NewLine);
162
163 debug_assert!(
166 s.lines().count() <= 1,
167 "calling write_line with embedded newlines is not allowed"
168 );
169
170 state.write_str(s)?;
173 state.write_str("\r\n")
174 }
175
176 fn write_str(&self, s: &str) -> std::io::Result<()> {
177 let mut state = self.state.lock().unwrap();
178 state.history.push(Move::Str(s.into()));
179 state.write_str(s)
180 }
181
182 fn clear_line(&self) -> std::io::Result<()> {
183 let mut state = self.state.lock().unwrap();
184 state.history.push(Move::Clear);
185 state.write_str("\r\x1b[2K")
186 }
187
188 fn flush(&self) -> std::io::Result<()> {
189 let mut state = self.state.lock().unwrap();
190 state.history.push(Move::Flush);
191 state.parser.flush()
192 }
193}
194
195struct InMemoryTermState {
196 width: u16,
197 height: u16,
198 parser: vt100::Parser,
199 history: Vec<Move>,
200}
201
202impl InMemoryTermState {
203 pub(crate) fn new(rows: u16, cols: u16) -> InMemoryTermState {
204 InMemoryTermState {
205 width: cols,
206 height: rows,
207 parser: Parser::new(rows, cols, 0),
208 history: vec![],
209 }
210 }
211
212 pub(crate) fn write_str(&mut self, s: &str) -> std::io::Result<()> {
213 self.parser.write_all(s.as_bytes())
214 }
215}
216
217impl Debug for InMemoryTermState {
218 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
219 f.debug_struct("InMemoryTermState").finish_non_exhaustive()
220 }
221}
222
223#[derive(Debug, PartialEq, Clone)]
224enum Move {
225 Up(usize),
226 Down(usize),
227 Left(usize),
228 Right(usize),
229 Str(String),
230 NewLine,
231 Clear,
232 Flush,
233}
234
235#[cfg(test)]
236mod test {
237 use super::*;
238
239 fn cursor_pos(in_mem: &InMemoryTerm) -> (u16, u16) {
240 in_mem
241 .state
242 .lock()
243 .unwrap()
244 .parser
245 .screen()
246 .cursor_position()
247 }
248
249 #[test]
250 fn line_wrapping() {
251 let in_mem = InMemoryTerm::new(10, 5);
252 assert_eq!(cursor_pos(&in_mem), (0, 0));
253
254 in_mem.write_str("ABCDE").unwrap();
255 assert_eq!(in_mem.contents(), "ABCDE");
256 assert_eq!(cursor_pos(&in_mem), (0, 5));
257 assert_eq!(
258 in_mem.moves_since_last_check(),
259 r#"Str("ABCDE")
260"#
261 );
262
263 in_mem.write_str("FG").unwrap();
265 assert_eq!(in_mem.contents(), "ABCDE\nFG");
266 assert_eq!(cursor_pos(&in_mem), (1, 2));
267 assert_eq!(
268 in_mem.moves_since_last_check(),
269 r#"Str("FG")
270"#
271 );
272
273 in_mem.write_str("HIJ").unwrap();
274 assert_eq!(in_mem.contents(), "ABCDE\nFGHIJ");
275 assert_eq!(cursor_pos(&in_mem), (1, 5));
276 assert_eq!(
277 in_mem.moves_since_last_check(),
278 r#"Str("HIJ")
279"#
280 );
281 }
282
283 #[test]
284 fn write_line() {
285 let in_mem = InMemoryTerm::new(10, 5);
286 assert_eq!(cursor_pos(&in_mem), (0, 0));
287
288 in_mem.write_line("A").unwrap();
289 assert_eq!(in_mem.contents(), "A");
290 assert_eq!(cursor_pos(&in_mem), (1, 0));
291 assert_eq!(
292 in_mem.moves_since_last_check(),
293 r#"Str("A")
294NewLine
295"#
296 );
297
298 in_mem.write_line("B").unwrap();
299 assert_eq!(in_mem.contents(), "A\nB");
300 assert_eq!(cursor_pos(&in_mem), (2, 0));
301 assert_eq!(
302 in_mem.moves_since_last_check(),
303 r#"Str("B")
304NewLine
305"#
306 );
307
308 in_mem.write_line("Longer than cols").unwrap();
309 assert_eq!(in_mem.contents(), "A\nB\nLonge\nr tha\nn col\ns");
310 assert_eq!(cursor_pos(&in_mem), (6, 0));
311 assert_eq!(
312 in_mem.moves_since_last_check(),
313 r#"Str("Longer than cols")
314NewLine
315"#
316 );
317 }
318
319 #[test]
320 fn basic_functionality() {
321 let in_mem = InMemoryTerm::new(10, 80);
322
323 in_mem.write_line("This is a test line").unwrap();
324 assert_eq!(in_mem.contents(), "This is a test line");
325 assert_eq!(
326 in_mem.moves_since_last_check(),
327 r#"Str("This is a test line")
328NewLine
329"#
330 );
331
332 in_mem.write_line("And another line!").unwrap();
333 assert_eq!(in_mem.contents(), "This is a test line\nAnd another line!");
334 assert_eq!(
335 in_mem.moves_since_last_check(),
336 r#"Str("And another line!")
337NewLine
338"#
339 );
340
341 in_mem.move_cursor_up(1).unwrap();
342 in_mem.write_str("TEST").unwrap();
343
344 assert_eq!(in_mem.contents(), "This is a test line\nTESTanother line!");
345 assert_eq!(
346 in_mem.moves_since_last_check(),
347 r#"Up(1)
348Str("TEST")
349"#
350 );
351 }
352
353 #[test]
354 fn newlines() {
355 let in_mem = InMemoryTerm::new(10, 10);
356 in_mem.write_line("LINE ONE").unwrap();
357 in_mem.write_line("LINE TWO").unwrap();
358 in_mem.write_line("").unwrap();
359 in_mem.write_line("LINE FOUR").unwrap();
360
361 assert_eq!(in_mem.contents(), "LINE ONE\nLINE TWO\n\nLINE FOUR");
362
363 assert_eq!(
364 in_mem.moves_since_last_check(),
365 r#"Str("LINE ONE")
366NewLine
367Str("LINE TWO")
368NewLine
369Str("")
370NewLine
371Str("LINE FOUR")
372NewLine
373"#
374 );
375 }
376
377 #[test]
378 fn cursor_zero_movement() {
379 let in_mem = InMemoryTerm::new(10, 80);
380 in_mem.write_line("LINE ONE").unwrap();
381 assert_eq!(cursor_pos(&in_mem), (1, 0));
382
383 in_mem.move_cursor_up(0).unwrap();
385 assert_eq!(cursor_pos(&in_mem), (1, 0));
386
387 in_mem.move_cursor_down(0).unwrap();
388 assert_eq!(cursor_pos(&in_mem), (1, 0));
389
390 in_mem.move_cursor_right(1).unwrap();
391 assert_eq!(cursor_pos(&in_mem), (1, 1));
392
393 in_mem.move_cursor_left(0).unwrap();
394 assert_eq!(cursor_pos(&in_mem), (1, 1));
395
396 in_mem.move_cursor_right(0).unwrap();
397 assert_eq!(cursor_pos(&in_mem), (1, 1));
398 }
399}