1use std::{
2 io,
3 sync::{mpsc::Sender, Arc, RwLock},
4 time::Duration,
5};
6
7use bytes::Bytes;
8use crossterm::{
9 event::{self, Event, KeyCode, KeyEventKind},
10 execute,
11 style::ResetColor,
12 terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
13};
14use portable_pty::{CommandBuilder, NativePtySystem, PtySize, PtySystem};
15use ratatui::{
16 backend::{Backend, CrosstermBackend},
17 layout::Alignment,
18 style::{Modifier, Style},
19 widgets::{Block, Borders, Paragraph},
20 Frame, Terminal,
21};
22use tui_term::widget::PseudoTerminal;
23use vt100::Screen;
24
25#[derive(Debug)]
26struct Size {
27 cols: u16,
28 rows: u16,
29}
30
31fn main() -> std::io::Result<()> {
32 let mut stdout = io::stdout();
33 execute!(stdout, ResetColor)?;
34 enable_raw_mode()?;
35 let mut stdout = io::stdout();
36 execute!(stdout, EnterAlternateScreen)?;
37 let backend = CrosstermBackend::new(stdout);
38 let mut terminal = Terminal::new(backend)?;
39
40 let pty_system = NativePtySystem::default();
41 let cwd = std::env::current_dir().unwrap();
42 let mut cmd = CommandBuilder::new_default_prog();
43 cmd.cwd(cwd);
44
45 let size = Size {
46 rows: terminal.size()?.height,
47 cols: terminal.size()?.width,
48 };
49
50 let pair = pty_system
51 .openpty(PtySize {
52 rows: size.rows,
53 cols: size.cols,
54 pixel_width: 0,
55 pixel_height: 0,
56 })
57 .unwrap();
58 std::thread::spawn(move || {
60 let mut child = pair.slave.spawn_command(cmd).unwrap();
61 let _child_exit_status = child.wait().unwrap();
62 drop(pair.slave);
63 });
64
65 let mut reader = pair.master.try_clone_reader().unwrap();
66 let parser = Arc::new(RwLock::new(vt100::Parser::new(size.rows, size.cols, 0)));
67
68 {
69 let parser = parser.clone();
70 std::thread::spawn(move || {
71 let mut buf = [0u8; 8192];
74 let mut processed_buf = Vec::new();
75 loop {
76 let size = reader.read(&mut buf).unwrap();
77 if size == 0 {
78 break;
79 }
80 if size > 0 {
81 processed_buf.extend_from_slice(&buf[..size]);
82 let mut parser = parser.write().unwrap();
83 parser.process(&processed_buf);
84
85 processed_buf.clear();
87 }
88 }
89 });
90 }
91
92 let (tx, rx) = std::sync::mpsc::channel::<Bytes>();
93
94 std::thread::spawn(move || {
96 let mut writer = pair.master.take_writer().unwrap();
97 while let Ok(bytes) = rx.recv() {
98 writer.write_all(&bytes).unwrap();
99 }
100 drop(pair.master);
101 });
102
103 run(&mut terminal, parser, tx)?;
104
105 disable_raw_mode()?;
107 execute!(terminal.backend_mut(), LeaveAlternateScreen,)?;
108 terminal.show_cursor()?;
109 println!("{size:?}");
110 Ok(())
111}
112
113fn run<B: Backend>(
114 terminal: &mut Terminal<B>,
115 parser: Arc<RwLock<vt100::Parser>>,
116 sender: Sender<Bytes>,
117) -> io::Result<()> {
118 loop {
119 terminal.draw(|f| ui(f, parser.read().unwrap().screen()))?;
120
121 if event::poll(Duration::from_millis(10))? {
123 match event::read()? {
126 Event::Key(key) => {
127 if key.kind == KeyEventKind::Press {
128 match key.code {
129 KeyCode::Char('q') => return Ok(()),
130 KeyCode::Char(input) => sender
131 .send(Bytes::from(input.to_string().into_bytes()))
132 .unwrap(),
133 KeyCode::Backspace => {
134 sender.send(Bytes::from(vec![8])).unwrap();
135 }
136 KeyCode::Enter => {
137 #[cfg(unix)]
138 sender.send(Bytes::from(vec![b'\n'])).unwrap();
139 #[cfg(windows)]
140 sender.send(Bytes::from(vec![b'\r', b'\n'])).unwrap();
141 }
142 KeyCode::Left => sender.send(Bytes::from(vec![27, 91, 68])).unwrap(),
143 KeyCode::Right => sender.send(Bytes::from(vec![27, 91, 67])).unwrap(),
144 KeyCode::Up => sender.send(Bytes::from(vec![27, 91, 65])).unwrap(),
145 KeyCode::Down => sender.send(Bytes::from(vec![27, 91, 66])).unwrap(),
146 KeyCode::Home => sender.send(Bytes::from(vec![27, 91, 72])).unwrap(),
147 KeyCode::End => sender.send(Bytes::from(vec![27, 91, 70])).unwrap(),
148 KeyCode::PageUp => {
149 sender.send(Bytes::from(vec![27, 91, 53, 126])).unwrap()
150 }
151 KeyCode::PageDown => {
152 sender.send(Bytes::from(vec![27, 91, 54, 126])).unwrap()
153 }
154 KeyCode::Tab => sender.send(Bytes::from(vec![9])).unwrap(),
155 KeyCode::BackTab => sender.send(Bytes::from(vec![27, 91, 90])).unwrap(),
156 KeyCode::Delete => {
157 sender.send(Bytes::from(vec![27, 91, 51, 126])).unwrap()
158 }
159 KeyCode::Insert => {
160 sender.send(Bytes::from(vec![27, 91, 50, 126])).unwrap()
161 }
162 KeyCode::F(_) => todo!(),
163 KeyCode::Null => todo!(),
164 KeyCode::Esc => todo!(),
165 KeyCode::CapsLock => todo!(),
166 KeyCode::ScrollLock => todo!(),
167 KeyCode::NumLock => todo!(),
168 KeyCode::PrintScreen => todo!(),
169 KeyCode::Pause => todo!(),
170 KeyCode::Menu => todo!(),
171 KeyCode::KeypadBegin => todo!(),
172 KeyCode::Media(_) => todo!(),
173 KeyCode::Modifier(_) => todo!(),
174 }
175 }
176 }
177 Event::FocusGained => {}
178 Event::FocusLost => {}
179 Event::Mouse(_) => {}
180 Event::Paste(_) => todo!(),
181 Event::Resize(cols, rows) => {
182 parser.write().unwrap().set_size(rows, cols);
183 }
184 }
185 }
186 }
187}
188
189fn ui(f: &mut Frame, screen: &Screen) {
190 let chunks = ratatui::layout::Layout::default()
191 .direction(ratatui::layout::Direction::Vertical)
192 .margin(1)
193 .constraints(
194 [
195 ratatui::layout::Constraint::Percentage(100),
196 ratatui::layout::Constraint::Min(1),
197 ]
198 .as_ref(),
199 )
200 .split(f.area());
201 let block = Block::default()
202 .borders(Borders::ALL)
203 .style(Style::default().add_modifier(Modifier::BOLD));
204 let pseudo_term = PseudoTerminal::new(screen).block(block);
205 f.render_widget(pseudo_term, chunks[0]);
206 let explanation = "Press q to exit".to_string();
207 let explanation = Paragraph::new(explanation)
208 .style(Style::default().add_modifier(Modifier::BOLD | Modifier::REVERSED))
209 .alignment(Alignment::Center);
210 f.render_widget(explanation, chunks[1]);
211}