Skip to main content

nested_shell/
nested_shell.rs

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