ratatui_toolkit/primitives/termtui/
parser.rs

1//! Terminal parser - bridge between termwiz and Screen
2
3use crate::primitives::termtui::screen::Screen;
4use crate::primitives::termtui::size::Size;
5use std::io::Write;
6use termwiz::escape::parser::Parser as TermwizParser;
7
8/// Terminal parser that processes VT100 escape sequences
9///
10/// Uses termwiz for parsing and delegates state management to Screen
11pub struct Parser {
12    /// The termwiz parser
13    parser: TermwizParser,
14    /// The terminal screen state
15    screen: Screen,
16}
17
18impl Parser {
19    /// Create a new parser
20    pub fn new(rows: usize, cols: usize, scrollback: usize) -> Self {
21        Self {
22            parser: TermwizParser::new(),
23            screen: Screen::new(rows, cols, scrollback),
24        }
25    }
26
27    /// Process bytes and update terminal state
28    pub fn process(&mut self, bytes: &[u8]) {
29        self.parser.parse(bytes, |action| {
30            self.screen.handle_action(action);
31        });
32    }
33
34    /// Get the screen state
35    pub fn screen(&self) -> &Screen {
36        &self.screen
37    }
38
39    /// Get mutable screen state
40    pub fn screen_mut(&mut self) -> &mut Screen {
41        &mut self.screen
42    }
43
44    /// Resize the terminal
45    pub fn resize(&mut self, rows: usize, cols: usize) {
46        self.screen.resize(rows, cols);
47    }
48
49    /// Get screen size
50    pub fn size(&self) -> Size {
51        self.screen.size()
52    }
53
54    /// Get scrollback offset
55    pub fn scrollback(&self) -> usize {
56        self.screen.scrollback()
57    }
58
59    /// Set scrollback offset
60    pub fn set_scrollback(&mut self, offset: usize) {
61        self.screen.set_scrollback(offset);
62    }
63}
64
65impl Write for Parser {
66    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
67        self.process(buf);
68        Ok(buf.len())
69    }
70
71    fn flush(&mut self) -> std::io::Result<()> {
72        Ok(())
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_parser_new() {
82        let parser = Parser::new(24, 80, 1000);
83        assert_eq!(parser.size().rows, 24);
84        assert_eq!(parser.size().cols, 80);
85    }
86
87    #[test]
88    fn test_parser_process_text() {
89        let mut parser = Parser::new(24, 80, 1000);
90        parser.process(b"Hello");
91
92        assert_eq!(parser.screen().cursor_pos().col, 5);
93    }
94
95    #[test]
96    fn test_parser_process_escape() {
97        let mut parser = Parser::new(24, 80, 1000);
98
99        // Move cursor to row 2, col 3 (1-indexed in escape sequences)
100        parser.process(b"\x1b[2;3H");
101
102        assert_eq!(parser.screen().cursor_pos().row, 1); // 0-indexed
103        assert_eq!(parser.screen().cursor_pos().col, 2); // 0-indexed
104    }
105
106    #[test]
107    fn test_parser_write_trait() {
108        let mut parser = Parser::new(24, 80, 1000);
109
110        // Use Write trait
111        write!(parser, "Test").unwrap();
112
113        assert_eq!(parser.screen().cursor_pos().col, 4);
114    }
115
116    #[test]
117    fn test_parser_resize() {
118        let mut parser = Parser::new(24, 80, 1000);
119
120        parser.resize(40, 120);
121        assert_eq!(parser.size().rows, 40);
122        assert_eq!(parser.size().cols, 120);
123    }
124}